diff --git a/mustache-sharp.test/FormatCompilerTester.cs b/mustache-sharp.test/FormatCompilerTester.cs
index 694bb66..3e37c24 100644
--- a/mustache-sharp.test/FormatCompilerTester.cs
+++ b/mustache-sharp.test/FormatCompilerTester.cs
@@ -1093,7 +1093,7 @@ Item Number: foo
return new TagParameter[] { new TagParameter("param") { IsRequired = false, DefaultValue = 123 } };
}
- public override void GetText(TextWriter writer, Dictionary arguments, object contextData)
+ public override void GetText(TextWriter writer, Dictionary arguments, Scope contextScope)
{
writer.Write(arguments["param"]);
}
@@ -1168,5 +1168,23 @@ Your order total was: $7.50";
}
#endregion
+
+ #region Context Variables
+
+ ///
+ /// We will use the index variable to determine whether or not to print out a line.
+ ///
+ [TestMethod]
+ public void TestCompile_CanUseContextVariablesToMakeDecisions()
+ {
+ FormatCompiler compiler = new FormatCompiler();
+ const string format = @"{{#each this}}{{#if @index}}{{#index}}{{/if}}{{/each}}";
+ Generator generator = compiler.Compile(format);
+ string actual = generator.Render(new int[] { 1, 1, 1, 1, });
+ string expected = "123";
+ Assert.AreEqual(expected, actual, "The numbers were not valid.");
+ }
+
+ #endregion
}
}
diff --git a/mustache-sharp/ArgumentCollection.cs b/mustache-sharp/ArgumentCollection.cs
index 0bdafe2..93e2518 100644
--- a/mustache-sharp/ArgumentCollection.cs
+++ b/mustache-sharp/ArgumentCollection.cs
@@ -50,9 +50,10 @@ namespace Mustache
///
/// Substitutes the key placeholders with their respective values.
///
- /// The current lexical scope.
+ /// The key/value pairs in the current lexical scope.
+ /// The key/value pairs in current context.
/// A dictionary associating the parameter name to the associated value.
- public Dictionary GetArguments(KeyScope scope)
+ public Dictionary GetArguments(Scope keyScope, Scope contextScope)
{
Dictionary arguments = new Dictionary();
foreach (KeyValuePair pair in _argumentLookup)
@@ -62,13 +63,27 @@ namespace Mustache
{
value = pair.Key.DefaultValue;
}
+ else if (pair.Value.StartsWith("@"))
+ {
+ value = contextScope.Find(pair.Value.Substring(1));
+ }
else
{
- value = scope.Find(pair.Value);
+ value = keyScope.Find(pair.Value);
}
arguments.Add(pair.Key.Name, value);
}
return arguments;
}
+
+ public Dictionary GetArguments()
+ {
+ Dictionary arguments = new Dictionary();
+ foreach (KeyValuePair pair in _argumentLookup)
+ {
+ arguments.Add(pair.Key.Name, pair.Value);
+ }
+ return arguments;
+ }
}
}
diff --git a/mustache-sharp/CompoundGenerator.cs b/mustache-sharp/CompoundGenerator.cs
index 8cad303..2af4fcb 100644
--- a/mustache-sharp/CompoundGenerator.cs
+++ b/mustache-sharp/CompoundGenerator.cs
@@ -59,10 +59,10 @@ namespace Mustache
}
}
- void IGenerator.GetText(KeyScope scope, TextWriter writer, object contextData)
+ void IGenerator.GetText(Scope keyScope, TextWriter writer, Scope contextScope)
{
- Dictionary arguments = _arguments.GetArguments(scope);
- IEnumerable contexts = _definition.GetChildContext(writer, scope, arguments);
+ Dictionary arguments = _arguments.GetArguments(keyScope, contextScope);
+ IEnumerable contexts = _definition.GetChildContext(writer, keyScope, arguments, contextScope);
List generators;
if (_definition.ShouldGeneratePrimaryGroup(arguments))
{
@@ -80,7 +80,7 @@ namespace Mustache
{
foreach (IGenerator generator in generators)
{
- generator.GetText(context.KeyScope ?? scope, context.Writer ?? writer, context.Data);
+ generator.GetText(context.KeyScope ?? keyScope, context.Writer ?? writer, context.ContextScope);
if (context.WriterNeedsConsidated)
{
writer.Write(_definition.ConsolidateWriter(context.Writer ?? writer, arguments));
diff --git a/mustache-sharp/EachTagDefinition.cs b/mustache-sharp/EachTagDefinition.cs
index 30c8e2f..163d7cb 100644
--- a/mustache-sharp/EachTagDefinition.cs
+++ b/mustache-sharp/EachTagDefinition.cs
@@ -43,10 +43,14 @@ namespace Mustache
/// Gets the context to use when building the inner text of the tag.
///
/// The text writer passed
- /// The current scope.
+ /// The current scope.
/// The arguments passed to the tag.
/// The scope to use when building the inner text of the tag.
- public override IEnumerable GetChildContext(TextWriter writer, KeyScope scope, Dictionary arguments)
+ public override IEnumerable GetChildContext(
+ TextWriter writer,
+ Scope keyScope,
+ Dictionary arguments,
+ Scope contextScope)
{
object value = arguments[collectionParameter];
IEnumerable enumerable = value as IEnumerable;
@@ -57,7 +61,14 @@ namespace Mustache
int index = 0;
foreach (object item in enumerable)
{
- yield return new NestedContext() { KeyScope = scope.CreateChildScope(item), Writer = writer, Data = index };
+ NestedContext childContext = new NestedContext()
+ {
+ KeyScope = keyScope.CreateChildScope(item),
+ Writer = writer,
+ ContextScope = contextScope.CreateChildScope(),
+ };
+ childContext.ContextScope.Set("index", index);
+ yield return childContext;
++index;
}
}
diff --git a/mustache-sharp/FormatCompiler.cs b/mustache-sharp/FormatCompiler.cs
index 5593fbd..198d282 100644
--- a/mustache-sharp/FormatCompiler.cs
+++ b/mustache-sharp/FormatCompiler.cs
@@ -39,6 +39,8 @@ namespace Mustache
_tagLookup.Add(withDefinition.Name, withDefinition);
NewlineTagDefinition newlineDefinition = new NewlineTagDefinition();
_tagLookup.Add(newlineDefinition.Name, newlineDefinition);
+ SetTagDefinition setDefinition = new SetTagDefinition();
+ _tagLookup.Add(setDefinition.Name, setDefinition);
}
///
@@ -150,9 +152,11 @@ namespace Mustache
foreach (TagParameter parameter in definition.Parameters)
{
regexBuilder.Append(@"(\s+?");
- regexBuilder.Append(@"(?");
+ regexBuilder.Append(@"(?(");
regexBuilder.Append(RegexHelper.CompoundKey);
- regexBuilder.Append(@"))");
+ regexBuilder.Append("|@");
+ regexBuilder.Append(RegexHelper.Key);
+ regexBuilder.Append(@")))");
if (!parameter.IsRequired)
{
regexBuilder.Append("?");
diff --git a/mustache-sharp/Generator.cs b/mustache-sharp/Generator.cs
index 0553e12..803c068 100644
--- a/mustache-sharp/Generator.cs
+++ b/mustache-sharp/Generator.cs
@@ -70,7 +70,7 @@ namespace Mustache
private string render(IFormatProvider provider, object source)
{
- KeyScope scope = new KeyScope(source);
+ Scope scope = new Scope(source);
foreach (EventHandler handler in _foundHandlers)
{
scope.KeyFound += handler;
@@ -80,7 +80,8 @@ namespace Mustache
scope.KeyNotFound += handler;
}
StringWriter writer = new StringWriter(provider);
- _generator.GetText(scope, writer, null);
+ Scope contextScope = new Scope(new Dictionary());
+ _generator.GetText(scope, writer, contextScope);
return writer.ToString();
}
}
diff --git a/mustache-sharp/IGenerator.cs b/mustache-sharp/IGenerator.cs
index 4ddb7ce..6b2fd24 100644
--- a/mustache-sharp/IGenerator.cs
+++ b/mustache-sharp/IGenerator.cs
@@ -11,10 +11,10 @@ namespace Mustache
///
/// Generates the text when applying the format plan.
///
- /// The current lexical scope of the keys.
+ /// The current lexical scope of the keys.
/// The text writer to send all text to.
- /// The data associated to the context.
+ /// The data associated to the context.
/// The generated text.
- void GetText(KeyScope scope, TextWriter writer, object contextData);
+ void GetText(Scope keyScope, TextWriter writer, Scope contextScope);
}
}
diff --git a/mustache-sharp/IndexTagDefinition.cs b/mustache-sharp/IndexTagDefinition.cs
index 86d1759..1d965a7 100644
--- a/mustache-sharp/IndexTagDefinition.cs
+++ b/mustache-sharp/IndexTagDefinition.cs
@@ -13,28 +13,23 @@ namespace Mustache
/// Initializes a new instance of an IndexTagDefinition.
///
public IndexTagDefinition()
- : base("index")
+ : base("index", true)
{
}
- ///
- /// Gets whether the tag only exists within the scope of its parent.
- ///
- protected override bool GetIsContextSensitive()
- {
- return true;
- }
-
///
/// Gets the text to output.
///
/// The writer to write the output to.
/// The arguments passed to the tag.
- /// Extra data passed along with the context.
- public override void GetText(TextWriter writer, Dictionary arguments, object contextData)
+ /// Extra data passed along with the context.
+ public override void GetText(TextWriter writer, Dictionary arguments, Scope contextScope)
{
- int index = (int)contextData;
- writer.Write(index);
+ object index;
+ if (contextScope.TryFind("index", out index))
+ {
+ writer.Write(index);
+ }
}
}
}
diff --git a/mustache-sharp/InlineGenerator.cs b/mustache-sharp/InlineGenerator.cs
index cf07ea7..7f5ac12 100644
--- a/mustache-sharp/InlineGenerator.cs
+++ b/mustache-sharp/InlineGenerator.cs
@@ -5,7 +5,7 @@ using System.IO;
namespace Mustache
{
///
- /// Generates the text for a tag that only exists on a single line.
+ /// Generates the text for a tag that is replaced with its generated text.
///
internal sealed class InlineGenerator : IGenerator
{
@@ -23,10 +23,18 @@ namespace Mustache
_arguments = arguments;
}
- void IGenerator.GetText(KeyScope scope, TextWriter writer, object contextData)
+ void IGenerator.GetText(Scope scope, TextWriter writer, Scope context)
{
- Dictionary arguments = _arguments.GetArguments(scope);
- _definition.GetText(writer, arguments, contextData);
+ Dictionary arguments;
+ if (_definition.IsSetter)
+ {
+ arguments = _arguments.GetArguments();
+ }
+ else
+ {
+ arguments = _arguments.GetArguments(scope, context);
+ }
+ _definition.GetText(writer, arguments, context);
}
}
}
diff --git a/mustache-sharp/KeyGenerator.cs b/mustache-sharp/KeyGenerator.cs
index 599ff2a..6f40026 100644
--- a/mustache-sharp/KeyGenerator.cs
+++ b/mustache-sharp/KeyGenerator.cs
@@ -42,7 +42,7 @@ namespace Mustache
return formatBuilder.ToString();
}
- void IGenerator.GetText(KeyScope scope, TextWriter writer, object contextData)
+ void IGenerator.GetText(Scope scope, TextWriter writer, Scope context)
{
object value = scope.Find(_key);
writer.Write(_format, value);
diff --git a/mustache-sharp/NestedContext.cs b/mustache-sharp/NestedContext.cs
index 1a0cacb..3c98c8b 100644
--- a/mustache-sharp/NestedContext.cs
+++ b/mustache-sharp/NestedContext.cs
@@ -19,36 +19,24 @@ namespace Mustache
/// Gets or sets the writer to use when generating the child context.
///
/// Setting the writer to null will indicate that the tag's writer should be used.
- public TextWriter Writer
- {
- get;
- set;
- }
+ public TextWriter Writer { get; set; }
///
/// Gets or sets whether the text sent to the returned writer needs to be added
/// to the parent tag's writer. This should be false if the parent writer is
/// being returned or is being wrapped.
///
- public bool WriterNeedsConsidated
- {
- get;
- set;
- }
+ public bool WriterNeedsConsidated { get; set; }
///
- /// Gets or sets the scope to use when generating the child context.
+ /// Gets or sets the key scope to use when generating the child context.
///
/// Setting the scope to null will indicate that the current scope should be used.
- public KeyScope KeyScope
- {
- get;
- set;
- }
+ public Scope KeyScope { get; set; }
///
/// Gets or sets data associated with the context.
///
- public object Data { get; set; }
+ public Scope ContextScope { get; set; }
}
}
diff --git a/mustache-sharp/NewlineTagDefinition.cs b/mustache-sharp/NewlineTagDefinition.cs
index e814fbd..dca77e4 100644
--- a/mustache-sharp/NewlineTagDefinition.cs
+++ b/mustache-sharp/NewlineTagDefinition.cs
@@ -22,8 +22,8 @@ namespace Mustache
///
/// The writer to write the output to.
/// The arguments passed to the tag.
- /// Extra data passed along with the context.
- public override void GetText(TextWriter writer, Dictionary arguments, object contextData)
+ /// Extra data passed along with the context.
+ public override void GetText(TextWriter writer, Dictionary arguments, Scope context)
{
writer.Write(Environment.NewLine);
}
diff --git a/mustache-sharp/RegexHelper.cs b/mustache-sharp/RegexHelper.cs
index 58cf7a3..c6738cb 100644
--- a/mustache-sharp/RegexHelper.cs
+++ b/mustache-sharp/RegexHelper.cs
@@ -8,7 +8,7 @@ namespace Mustache
///
public static class RegexHelper
{
- private const string Key = @"[_\w][_\w\d]*";
+ internal const string Key = @"[_\w][_\w\d]*";
internal const string CompoundKey = Key + @"(\." + Key + ")*";
///
diff --git a/mustache-sharp/KeyScope.cs b/mustache-sharp/Scope.cs
similarity index 55%
rename from mustache-sharp/KeyScope.cs
rename to mustache-sharp/Scope.cs
index 7e176d9..9b7e850 100644
--- a/mustache-sharp/KeyScope.cs
+++ b/mustache-sharp/Scope.cs
@@ -9,16 +9,16 @@ namespace Mustache
///
/// Represents a scope of keys.
///
- public sealed class KeyScope
+ public sealed class Scope
{
private readonly object _source;
- private readonly KeyScope _parent;
+ private readonly Scope _parent;
///
/// Initializes a new instance of a KeyScope.
///
/// The object to search for keys in.
- internal KeyScope(object source)
+ internal Scope(object source)
: this(source, null)
{
}
@@ -28,7 +28,7 @@ namespace Mustache
///
/// The object to search for keys in.
/// The parent scope to search in if the value is not found.
- internal KeyScope(object source, KeyScope parent)
+ internal Scope(object source, Scope parent)
{
_parent = parent;
_source = source;
@@ -44,14 +44,23 @@ namespace Mustache
///
public event EventHandler KeyNotFound;
+ ///
+ /// Creates a child scope that searches for keys in a default dictionary of key/value pairs.
+ ///
+ /// The new child scope.
+ public Scope CreateChildScope()
+ {
+ return CreateChildScope(new Dictionary());
+ }
+
///
/// Creates a child scope that searches for keys in the given object.
///
/// The object to search for keys in.
/// The new child scope.
- public KeyScope CreateChildScope(object source)
+ public Scope CreateChildScope(object source)
{
- KeyScope scope = new KeyScope(source, this);
+ Scope scope = new Scope(source, this);
scope.KeyFound = KeyFound;
scope.KeyNotFound = KeyNotFound;
return scope;
@@ -65,58 +74,45 @@ namespace Mustache
/// A key with the given name could not be found.
internal object Find(string name)
{
- string[] names = name.Split('.');
- string member = names[0];
- object nextLevel = _source;
- if (member != "this")
+ string member = null;
+ object value = null;
+ if (tryFind(name, ref member, ref value))
{
- nextLevel = find(name, member);
+ onKeyFound(name, ref value);
+ return value;
}
- for (int index = 1; index < names.Length; ++index)
+ if (onKeyNotFound(name, member, ref value))
{
- IDictionary context = toLookup(nextLevel);
- member = names[index];
- if (!context.TryGetValue(member, out nextLevel))
- {
- nextLevel = handleKeyNotFound(name, member);
- }
+ return value;
}
+ string message = String.Format(CultureInfo.CurrentCulture, Resources.KeyNotFound, member);
+ throw new KeyNotFoundException(message);
+ }
+
+ private void onKeyFound(string name, ref object value)
+ {
if (KeyFound != null)
{
- KeyFoundEventArgs args = new KeyFoundEventArgs(name, nextLevel);
+ KeyFoundEventArgs args = new KeyFoundEventArgs(name, value);
KeyFound(this, args);
- nextLevel = args.Substitute;
+ value = args.Substitute;
}
- return nextLevel;
}
- private object find(string fullName, string memberName)
+ private bool onKeyNotFound(string name, string member, ref object value)
{
- IDictionary lookup = toLookup(_source);
- if (lookup.ContainsKey(memberName))
+ if (KeyNotFound == null)
{
- return lookup[memberName];
+ return false;
}
- if (_parent == null)
+ KeyNotFoundEventArgs args = new KeyNotFoundEventArgs(name, member);
+ KeyNotFound(this, args);
+ if (!args.Handled)
{
- return handleKeyNotFound(fullName, memberName);
+ return false;
}
- return _parent.find(fullName, memberName);
- }
-
- private object handleKeyNotFound(string fullName, string memberName)
- {
- KeyNotFoundEventArgs args = new KeyNotFoundEventArgs(fullName, memberName);
- if (KeyNotFound != null)
- {
- KeyNotFound(this, args);
- }
- if (args.Handled)
- {
- return args.Substitute;
- }
- string message = String.Format(CultureInfo.CurrentCulture, Resources.KeyNotFound, memberName);
- throw new KeyNotFoundException(message);
+ value = args.Substitute;
+ return true;
}
private static IDictionary toLookup(object value)
@@ -128,5 +124,59 @@ namespace Mustache
}
return lookup;
}
+
+ internal void Set(string key, object value)
+ {
+ IDictionary lookup = toLookup(_source);
+ lookup[key] = value;
+ }
+
+ public bool TryFind(string name, out object value)
+ {
+ string member = null;
+ value = null;
+ return tryFind(name, ref member, ref value);
+ }
+
+ private bool tryFind(string name, ref string member, ref object value)
+ {
+ string[] names = name.Split('.');
+ member = names[0];
+ value = _source;
+ if (member != "this")
+ {
+ if (!tryFindFirst(member, ref value))
+ {
+ return false;
+ }
+ }
+ for (int index = 1; index < names.Length; ++index)
+ {
+ IDictionary context = toLookup(value);
+ member = names[index];
+ if (!context.TryGetValue(member, out value))
+ {
+ value = null;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private bool tryFindFirst(string member, ref object value)
+ {
+ IDictionary lookup = toLookup(_source);
+ if (lookup.ContainsKey(member))
+ {
+ value = lookup[member];
+ return true;
+ }
+ if (_parent == null)
+ {
+ value = null;
+ return false;
+ }
+ return _parent.tryFindFirst(member, ref value);
+ }
}
}
diff --git a/mustache-sharp/SetTagDefinition.cs b/mustache-sharp/SetTagDefinition.cs
new file mode 100644
index 0000000..21eede6
--- /dev/null
+++ b/mustache-sharp/SetTagDefinition.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mustache
+{
+ ///
+ /// Defines a tag that declares a named value in the current context.
+ ///
+ internal sealed class SetTagDefinition : InlineTagDefinition
+ {
+ private const string nameParameter = "name";
+ private static readonly TagParameter name = new TagParameter(nameParameter) { IsRequired = true };
+
+ ///
+ /// Initializes a new instance of an SetTagDefinition.
+ ///
+ public SetTagDefinition()
+ : base("set", true)
+ {
+ }
+
+ protected override bool GetIsSetter()
+ {
+ return true;
+ }
+
+ protected override IEnumerable GetParameters()
+ {
+ return new TagParameter[] { name };
+ }
+
+ ///
+ /// Gets the text to output.
+ ///
+ /// The writer to write the output to.
+ /// The arguments passed to the tag.
+ /// Extra data passed along with the context.
+ public override void GetText(TextWriter writer, Dictionary arguments, Scope contextScope)
+ {
+ string name = (string)arguments[nameParameter];
+ // TODO - get the value for the variable
+ object value = null;
+ contextScope.Set(name, value);
+ }
+ }
+}
diff --git a/mustache-sharp/StaticGenerator.cs b/mustache-sharp/StaticGenerator.cs
index 8ac8996..4068761 100644
--- a/mustache-sharp/StaticGenerator.cs
+++ b/mustache-sharp/StaticGenerator.cs
@@ -27,7 +27,7 @@ namespace Mustache
get { return value; }
}
- void IGenerator.GetText(KeyScope scope, TextWriter writer, object contextData)
+ void IGenerator.GetText(Scope scope, TextWriter writer, Scope context)
{
writer.Write(Value);
}
diff --git a/mustache-sharp/TagDefinition.cs b/mustache-sharp/TagDefinition.cs
index dec179f..429b131 100644
--- a/mustache-sharp/TagDefinition.cs
+++ b/mustache-sharp/TagDefinition.cs
@@ -44,6 +44,16 @@ namespace Mustache
get { return _tagName; }
}
+ internal bool IsSetter
+ {
+ get { return GetIsSetter(); }
+ }
+
+ protected virtual bool GetIsSetter()
+ {
+ return false;
+ }
+
///
/// Gets whether the tag is limited to the parent tag's context.
///
@@ -139,12 +149,22 @@ namespace Mustache
/// Gets the context to use when building the inner text of the tag.
///
/// The text writer passed
- /// The current scope.
+ /// The current key scope.
/// The arguments passed to the tag.
/// The scope to use when building the inner text of the tag.
- public virtual IEnumerable GetChildContext(TextWriter writer, KeyScope scope, Dictionary arguments)
+ public virtual IEnumerable GetChildContext(
+ TextWriter writer,
+ Scope keyScope,
+ Dictionary arguments,
+ Scope contextScope)
{
- yield return new NestedContext() { KeyScope = scope, Writer = writer };
+ NestedContext context = new NestedContext()
+ {
+ KeyScope = keyScope,
+ Writer = writer,
+ ContextScope = contextScope.CreateChildScope()
+ };
+ yield return context;
}
///
@@ -152,8 +172,8 @@ namespace Mustache
///
/// The text writer to write to.
/// The arguments passed to the tag.
- /// The data associated to the context.
- public virtual void GetText(TextWriter writer, Dictionary arguments, object contextData)
+ /// The data associated to the context.
+ public virtual void GetText(TextWriter writer, Dictionary arguments, Scope context)
{
}
diff --git a/mustache-sharp/WithGenerator.cs b/mustache-sharp/WithTagDefinition.cs
similarity index 77%
rename from mustache-sharp/WithGenerator.cs
rename to mustache-sharp/WithTagDefinition.cs
index 9a2c2c8..7ef40b5 100644
--- a/mustache-sharp/WithGenerator.cs
+++ b/mustache-sharp/WithTagDefinition.cs
@@ -50,13 +50,23 @@ namespace Mustache
/// Gets the context to use when building the inner text of the tag.
///
/// The text writer passed
- /// The current scope.
+ /// The current key scope.
/// The arguments passed to the tag.
/// The scope to use when building the inner text of the tag.
- public override IEnumerable GetChildContext(TextWriter writer, KeyScope scope, Dictionary arguments)
+ public override IEnumerable GetChildContext(
+ TextWriter writer,
+ Scope keyScope,
+ Dictionary arguments,
+ Scope contextScope)
{
- object context = arguments[contextParameter];
- yield return new NestedContext() { KeyScope = scope.CreateChildScope(context), Writer = writer };
+ object contextSource = arguments[contextParameter];
+ NestedContext context = new NestedContext()
+ {
+ KeyScope = keyScope.CreateChildScope(contextSource),
+ Writer = writer,
+ ContextScope = contextScope.CreateChildScope()
+ };
+ yield return context;
}
}
}
diff --git a/mustache-sharp/mustache-sharp.csproj b/mustache-sharp/mustache-sharp.csproj
index 55d74ea..38316ad 100644
--- a/mustache-sharp/mustache-sharp.csproj
+++ b/mustache-sharp/mustache-sharp.csproj
@@ -40,6 +40,7 @@
+
@@ -68,8 +69,8 @@
-
-
+
+