Initial Commit
This is the code almost verbatim from the NList project. This project is due for a major overhaul, but I don't know the order I will be breaking out NList in the upcoming weeks.
This commit is contained in:
parent
3f8bf7413b
commit
827faa5d6e
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<TestSettings name="Local" id="2bc42439-1bb6-4112-9c20-eca1ffcae064" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
|
||||
<Description>These are default test settings for a local test run.</Description>
|
||||
<Deployment enabled="false" />
|
||||
<Execution>
|
||||
<TestTypeSpecific />
|
||||
<AgentRule name="Execution Agents">
|
||||
</AgentRule>
|
||||
</Execution>
|
||||
</TestSettings>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<TestSettings name="Trace and Test Impact" id="535ebf31-4d23-42a7-a823-ecb179ff7886" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
|
||||
<Description>These are test settings for Trace and Test Impact.</Description>
|
||||
<Execution>
|
||||
<TestTypeSpecific />
|
||||
<AgentRule name="Execution Agents">
|
||||
<DataCollectors>
|
||||
<DataCollector uri="datacollector://microsoft/SystemInfo/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TestTools.DataCollection.SystemInfo.SystemInfoDataCollector, Microsoft.VisualStudio.TestTools.DataCollection.SystemInfo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="System Information">
|
||||
</DataCollector>
|
||||
<DataCollector uri="datacollector://microsoft/ActionLog/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TestTools.ManualTest.ActionLog.ActionLogPlugin, Microsoft.VisualStudio.TestTools.ManualTest.ActionLog, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="Actions">
|
||||
</DataCollector>
|
||||
<DataCollector uri="datacollector://microsoft/HttpProxy/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TraceCollector.HttpProxyCollector, Microsoft.VisualStudio.TraceCollector, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="ASP.NET Client Proxy for IntelliTrace and Test Impact">
|
||||
</DataCollector>
|
||||
<DataCollector uri="datacollector://microsoft/TestImpact/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TraceCollector.TestImpactDataCollector, Microsoft.VisualStudio.TraceCollector, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="Test Impact">
|
||||
</DataCollector>
|
||||
<DataCollector uri="datacollector://microsoft/TraceDebugger/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TraceCollector.TraceDebuggerDataCollector, Microsoft.VisualStudio.TraceCollector, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="IntelliTrace">
|
||||
</DataCollector>
|
||||
</DataCollectors>
|
||||
</AgentRule>
|
||||
</Execution>
|
||||
</TestSettings>
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mustache-sharp", "mustache-sharp\mustache-sharp.csproj", "{D71B378F-A4BA-4263-A4F0-07A49A0C528D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mustache-sharp.test", "mustache-sharp.test\mustache-sharp.test.csproj", "{7F607362-0680-4751-B1DC-621219294AE3}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{25414E49-67E6-4B8D-8AD8-78C70F8992A7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Local.testsettings = Local.testsettings
|
||||
mustache-sharp.vsmdi = mustache-sharp.vsmdi
|
||||
TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(TestCaseManagementSettings) = postSolution
|
||||
CategoryFile = mustache-sharp.vsmdi
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7F607362-0680-4751-B1DC-621219294AE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F607362-0680-4751-B1DC-621219294AE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F607362-0680-4751-B1DC-621219294AE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F607362-0680-4751-B1DC-621219294AE3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,524 @@
|
|||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace mustache.test
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the Formatter class.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class FormatterTester
|
||||
{
|
||||
#region Real World Example
|
||||
|
||||
/// <summary>
|
||||
/// The Formatter class is especially useful when performing simple mail merge operations.
|
||||
/// Like String.Format, Formatter will substitute placeholders for actual values. In the case
|
||||
/// of Formatter, placeholders are indicated by name, rather than index and are wrapped with
|
||||
/// double curly braces: {{name}}. The name within the curly brace can include any characters,
|
||||
/// including whitespace, except for two or more adjacent right curly braces (}}).
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ReplaceNamedPlaceholdersWithFormats()
|
||||
{
|
||||
const string format = "Hello {{name}}! It is {{date:MM-dd-yyyy}}. You make {{income:C}} an hour.";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result1 = formatter.Format(new Dictionary<string, object>()
|
||||
{
|
||||
{ "name", "Bob" },
|
||||
{ "date", new DateTime(2012, 03, 11) },
|
||||
{ "income", 32.8 }
|
||||
});
|
||||
Assert.AreEqual("Hello Bob! It is 03-11-2012. You make $32.80 an hour.", result1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we want to work with objects, rather than raw dictionaries, we can wrap the objects with
|
||||
/// property dictionaries.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_UseObject()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Date = new DateTime(2012, 03, 11),
|
||||
Income = 32.8
|
||||
};
|
||||
const string format = "Hello {{Name}}! It is {{Date:MM-dd-yyyy}}. You make {{Income:C}} an hour.";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result1 = formatter.Format(person);
|
||||
Assert.AreEqual("Hello Bob! It is 03-11-2012. You make $32.80 an hour.", result1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We can the Formatter to print out a list of items following a format.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_PrintList()
|
||||
{
|
||||
List<int> values = new List<int>() { 0, 1, 2, 3, 4 };
|
||||
const string format = "{{#each this}}{{this}} {{/each}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(values);
|
||||
Assert.AreEqual("0 1 2 3 4 ", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We can include some text conditionally.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionallyIncludeText()
|
||||
{
|
||||
Random random = new Random();
|
||||
int value = random.Next();
|
||||
bool isEven = value % 2 == 0;
|
||||
var data = new
|
||||
{
|
||||
Value = value,
|
||||
IsEven = isEven,
|
||||
};
|
||||
const string format = "{{Value}} {{#if IsEven}}is even{{#else}}is odd{{/if}}.";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(data);
|
||||
string expected = String.Format("{0}", value) + (isEven ? " is even." : " is odd.");
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiple cases can be handled using if/elif/else.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_HandleCases()
|
||||
{
|
||||
const string format = @"{{#if No}}No{{#elif Yes}}Yes{{#else}}Maybe{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
var data = new
|
||||
{
|
||||
Yes = true,
|
||||
No = false,
|
||||
};
|
||||
string result = formatter.Format(data);
|
||||
Assert.AreEqual("Yes", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should be able to combine tags anyway we want.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_Compound()
|
||||
{
|
||||
const string format = @"{{#with Customer}}
|
||||
Hello{{#if FirstName}} {{FirstName}}{{/if}}:
|
||||
{{/with}}
|
||||
{{#! We only want to print out purchases if they have some. }}
|
||||
{{#if Purchases}}
|
||||
|
||||
You recently purchased:
|
||||
{{#each Purchases}}
|
||||
{{Name}}: {{Quantity}} x {{Price:C}}
|
||||
{{/each}}
|
||||
Your total was: {{Total:C}}
|
||||
{{/if}}
|
||||
|
||||
We thought you might be interested in buying: {{PromotionProduct}}.
|
||||
|
||||
Thank you,
|
||||
{{#with Agent}}
|
||||
{{Name}}
|
||||
{{/with}}";
|
||||
|
||||
Formatter formatter = new Formatter(format);
|
||||
var data = new
|
||||
{
|
||||
Customer = new
|
||||
{
|
||||
FirstName = "Bob",
|
||||
},
|
||||
Purchases = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
Name = "Donkey",
|
||||
Quantity = 8,
|
||||
Price = 1.23m,
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Hammer",
|
||||
Quantity = 1,
|
||||
Price = 8.32m,
|
||||
},
|
||||
},
|
||||
Total = 18.16m,
|
||||
PromotionProduct = "Sneakers",
|
||||
Agent = new
|
||||
{
|
||||
Name = "Tom",
|
||||
},
|
||||
};
|
||||
string result = formatter.Format(data);
|
||||
Assert.AreEqual(@"Hello Bob:
|
||||
|
||||
You recently purchased:
|
||||
Donkey: 8 x $1.23
|
||||
Hammer: 1 x $8.32
|
||||
Your total was: $18.16
|
||||
|
||||
We thought you might be interested in buying: Sneakers.
|
||||
|
||||
Thank you,
|
||||
Tom
|
||||
", result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Argument Checking
|
||||
|
||||
/// <summary>
|
||||
/// An exception should be thrown if the format string is null.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void TestCtor_NullFormat_ThrowsException()
|
||||
{
|
||||
string format = null;
|
||||
new Formatter(format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we try to replace a placeholder that we do not have a lookup key for,
|
||||
/// an exception should be thrown.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(KeyNotFoundException))]
|
||||
public void TestFormat_MissingKey_ThrowsException()
|
||||
{
|
||||
Formatter formatter = new Formatter("{{unknown}}");
|
||||
IDictionary<string, object> lookup = new Dictionary<string, object>();
|
||||
formatter.Format(lookup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A format exception should be thrown if there is not a matching closing if tag.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void TestFormat_MissingClosingIfTag_ThrowsException()
|
||||
{
|
||||
new Formatter("{{#if Bob}}Hello");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A format exception should be thrown if the matching closing tag is wrong.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void TestFormat_WrongClosingIfTag_ThrowsException()
|
||||
{
|
||||
new Formatter("{{#with this}}{{#if Bob}}Hello{{/with}}{{/if}}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// If we specify a right alignment, the output should be aligned to the right.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_WithRightAlignment_AlignsToRight()
|
||||
{
|
||||
string format = "{{Name,10}}";
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob"
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
string result = Formatter.Format(format, dictionary);
|
||||
Assert.AreEqual(" Bob", result, "The text was not aligned.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we specify a left alignment, the output should be aligned to the left.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_WithLeftAlignment_AlignsToLeft()
|
||||
{
|
||||
string format = "{{Name,-10}}";
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob"
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
string result = Formatter.Format(null, format, dictionary);
|
||||
Assert.AreEqual("Bob ", result, "The text was not aligned.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we try to format an empty string, an empty string should be returned.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_EmptyFormat_ReturnsEmpty()
|
||||
{
|
||||
Formatter formatter = new Formatter(String.Empty);
|
||||
Dictionary<string, object> lookup = new Dictionary<string, object>();
|
||||
string result = formatter.Format(lookup);
|
||||
Assert.AreEqual(String.Empty, result, "The result should have been empty.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If our format string is just a placeholder, than just the replacement value should be returned.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_FormatIsSinglePlaceholder_ReturnsReplaced()
|
||||
{
|
||||
Formatter formatter = new Formatter("{{name}}");
|
||||
Dictionary<string, object> lookup = new Dictionary<string, object>()
|
||||
{
|
||||
{ "name", "test" }
|
||||
};
|
||||
string result = formatter.Format(lookup);
|
||||
Assert.AreEqual("test", result, "The result was wrong.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should be able to put just about anything inside of a placeholder, but it will
|
||||
/// not be treated like a placeholder.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_PlaceholderContainsSpecialCharacters_ReturnsUnreplaced()
|
||||
{
|
||||
Formatter formatter = new Formatter("{{ \\_@#$%^ }1233 abc}}");
|
||||
Dictionary<string, object> lookup = new Dictionary<string, object>()
|
||||
{
|
||||
{ " \\_@#$%^ }1233 abc", "test" }
|
||||
};
|
||||
string result = formatter.Format(lookup);
|
||||
Assert.AreEqual("{{ \\_@#$%^ }1233 abc}}", result, "The result was wrong.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a lookup value is null, it should be replaced with an empty string.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_NullValue_ReplacesWithBlank()
|
||||
{
|
||||
Formatter formatter = new Formatter("These quotes should be empty '{{name}}'.");
|
||||
Dictionary<string, object> lookup = new Dictionary<string, object>()
|
||||
{
|
||||
{ "name", null }
|
||||
};
|
||||
string result = formatter.Format(lookup);
|
||||
Assert.AreEqual("These quotes should be empty ''.", result, "The result was wrong.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a replacement value contains a placeholder, it should NOT be evaluated.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ReplacementContainsPlaceholder_IgnoresPlaceholder()
|
||||
{
|
||||
Formatter formatter = new Formatter("The length of {{name}} is {{length}}.");
|
||||
Dictionary<string, object> lookup = new Dictionary<string, object>()
|
||||
{
|
||||
{ "name", "Bob" },
|
||||
{ "length", "{{name}}" }
|
||||
};
|
||||
string result = formatter.Format(lookup);
|
||||
Assert.AreEqual("The length of Bob is {{name}}.", result, "The result was wrong.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we pass null to as the format provider to the Format function,
|
||||
/// the current culture is used.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_NullFormatter_UsesCurrentCulture()
|
||||
{
|
||||
string format = "{0:C}";
|
||||
Formatter formatter = new Formatter("{" + format + "}");
|
||||
string result = formatter.Format((IFormatProvider)null, new Dictionary<string, object>() { { "0", 28.30m } });
|
||||
string expected = String.Format(CultureInfo.CurrentCulture, format, 28.30m);
|
||||
Assert.AreEqual(expected, result, "The wrong format provider was used.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we put a tag on a line by itself, it shouldn't result in any whitespace.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_TagOnLineByItself_NoNewlineGenerated()
|
||||
{
|
||||
const string format = @"Hello
|
||||
{{#if Name}}
|
||||
{{Name}}
|
||||
{{/if}}
|
||||
Goodbye
|
||||
";
|
||||
var data = new { Name = "George" };
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(data);
|
||||
const string expected = @"Hello
|
||||
George
|
||||
Goodbye
|
||||
";
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a key is not found at the current level, it is looked for at the parent level.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_NameAtHigherScope_Finds()
|
||||
{
|
||||
const string format = "{{#with Child}}{{TopLevel}} and {{ChildLevel}}{{/with}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
var data = new
|
||||
{
|
||||
TopLevel = "Parent",
|
||||
Child = new { ChildLevel = "Child" },
|
||||
};
|
||||
string result = formatter.Format(data);
|
||||
Assert.AreEqual("Parent and Child", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null values are considered false by if statements.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionOnNull_ConsideredFalse()
|
||||
{
|
||||
const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(null);
|
||||
Assert.AreEqual("Good", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty collections are considered false by if statements.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionOnEmptyCollection_ConsideredFalse()
|
||||
{
|
||||
const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(new object[0]);
|
||||
Assert.AreEqual("Good", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Non-empty collections are considered true by if statements.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionOnNonEmptyCollection_ConsideredTrue()
|
||||
{
|
||||
const string format = "{{#if this}}Good{{#else}}Bad{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(new object[1]);
|
||||
Assert.AreEqual("Good", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null-char is considered false by if statements.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionOnNullChar_ConsideredFalse()
|
||||
{
|
||||
const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format('\0');
|
||||
Assert.AreEqual("Good", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zero is considered false by if statements.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionOnZero_ConsideredFalse()
|
||||
{
|
||||
const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
int? value = 0;
|
||||
string result = formatter.Format(value);
|
||||
Assert.AreEqual("Good", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Everything else is considered true by if statements.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_ConditionOnDateTime_ConsideredTrue()
|
||||
{
|
||||
const string format = "{{#if this}}Good{{#else}}Bad{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(DateTime.Now);
|
||||
Assert.AreEqual("Good", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instead of requiring deeply nested "with" statements, members
|
||||
/// can be separated by dots.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_NestedMembers_SearchesMembers()
|
||||
{
|
||||
const string format = "{{Customer.Name}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
var data = new { Customer = new { Name = "Bob" } };
|
||||
string result = formatter.Format(data);
|
||||
Assert.AreEqual("Bob", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keys should cause newlines to be respected, since they are considered content.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_KeyBetweenTags_RespectsTrailingNewline()
|
||||
{
|
||||
string format = "{{#each this}}{{this}} {{/each}}" + Environment.NewLine;
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format("Hello");
|
||||
Assert.AreEqual("H e l l o " + Environment.NewLine, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If someone tries to loop on a non-enumerable, it should do nothing.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_EachOnNonEnumerable_PrintsNothing()
|
||||
{
|
||||
const string format = "{{#each this}}Bad{{/each}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(123);
|
||||
Assert.AreEqual(String.Empty, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a tag header is on the same line as it's footer, the new-line should not be removed.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_InlineTags_RespectNewLine()
|
||||
{
|
||||
const string format = @"{{#if this}}{{/if}}
|
||||
";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(true);
|
||||
Assert.AreEqual(Environment.NewLine, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a tag header is on the same line as it's footer, the new-line should not be removed.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFormatter_TagFooterFollowedByTagHeader_RemovesNewLine()
|
||||
{
|
||||
const string format = @"{{#if this}}
|
||||
{{/if}}{{#if this}}
|
||||
Hello{{/if}}";
|
||||
Formatter formatter = new Formatter(format);
|
||||
string result = formatter.Format(true);
|
||||
Assert.AreEqual("Hello", result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("mustache-sharp.test")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("mustache-sharp.test")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2013")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9975f293-f972-4751-9c8c-e25b17c0c8bc")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,484 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace mustache.test
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the PropertyDictionary class.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class PropertyDictionaryTester
|
||||
{
|
||||
#region Real World Example
|
||||
|
||||
/// <summary>
|
||||
/// The purpose of the PropertyDictionary class is to allow an object to be inspected,
|
||||
/// as if it were a dictionary. This means we can get and set properties by their names.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestPropertyDictionary_AccessPropertiesViaIndexer()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 23,
|
||||
Birthday = new DateTime(2012, 03, 12)
|
||||
};
|
||||
PropertyDictionary wrapper = new PropertyDictionary(person);
|
||||
|
||||
Assert.AreEqual(3, wrapper.Count, "The wrong number of properties were created.");
|
||||
|
||||
Assert.IsTrue(wrapper.ContainsKey("Name"));
|
||||
Assert.IsTrue(wrapper.ContainsKey("Age"));
|
||||
Assert.IsTrue(wrapper.ContainsKey("Birthday"));
|
||||
|
||||
Assert.AreEqual(person.Name, wrapper["Name"], "The name was not wrapped.");
|
||||
Assert.AreEqual(person.Age, wrapper["Age"], "The age was not wrapped.");
|
||||
Assert.AreEqual(person.Birthday, wrapper["Birthday"], "The birthday was not wrapped.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ctor & Instance & IsReadOnly
|
||||
|
||||
/// <summary>
|
||||
/// If we try to wrap null, an exception should be thrown.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestCtor_NullInstance_ThrowsException()
|
||||
{
|
||||
PropertyDictionary dictionary = new PropertyDictionary(null);
|
||||
Assert.AreEqual(0, dictionary.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should be able to access the underlying object.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestCtor_SetsInstance()
|
||||
{
|
||||
object instance = new object();
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
Assert.AreSame(instance, dictionary.Instance, "The instance was not set.");
|
||||
ICollection<KeyValuePair<string, object>> collection = dictionary;
|
||||
Assert.IsTrue(collection.IsReadOnly, "The collection should not have been read-only.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Add
|
||||
|
||||
/// <summary>
|
||||
/// Since the dictionary is a simple wrapper around an object, we cannot add new properties.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void TestAdd_IDictionary_ThrowsException()
|
||||
{
|
||||
IDictionary<string, object> dictionary = new PropertyDictionary(new object());
|
||||
dictionary.Add("Name", "Bob");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Since the dictionary is a simple wrapper around an object, we cannot add new properties.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void TestAdd_ICollection_ThrowsException()
|
||||
{
|
||||
ICollection<KeyValuePair<string, object>> collection = new PropertyDictionary(new object());
|
||||
collection.Add(new KeyValuePair<string, object>("Name", "Bob"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ContainsKey
|
||||
|
||||
/// <summary>
|
||||
/// If the wrapped object has a property, the key should be found.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContainsKey_PropertyExists_ReturnsTrue()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob",
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(person);
|
||||
bool result = dictionary.ContainsKey("Name");
|
||||
Assert.IsTrue(result, "The property name was not found.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the wrapped object does not have a property, the key should not be found.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContainsKey_PropertyMissing_ReturnsFalse()
|
||||
{
|
||||
var person = new { };
|
||||
PropertyDictionary dictionary = new PropertyDictionary(person);
|
||||
bool result = dictionary.ContainsKey("Name");
|
||||
Assert.IsFalse(result, "The property name was found.");
|
||||
}
|
||||
|
||||
private class BaseType
|
||||
{
|
||||
public string Inherited { get; set; }
|
||||
}
|
||||
|
||||
private class DerivedType : BaseType
|
||||
{
|
||||
public string Local { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should be able to see properties defined in the base type.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContainsKey_PropertyInherited_ReturnsTrue()
|
||||
{
|
||||
BaseType b = new DerivedType();
|
||||
PropertyDictionary dictionary = new PropertyDictionary(b);
|
||||
bool result = dictionary.ContainsKey("Inherited");
|
||||
Assert.IsTrue(result, "The property name was not found.");
|
||||
}
|
||||
|
||||
private class PrivateType
|
||||
{
|
||||
private string Hidden { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should not be able to see private properties.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContainsKey_PropertyPrivate_ReturnsFalse()
|
||||
{
|
||||
PrivateType t = new PrivateType();
|
||||
PropertyDictionary dictionary = new PropertyDictionary(t);
|
||||
bool result = dictionary.ContainsKey("Hidden");
|
||||
Assert.IsFalse(result, "The property name was found.");
|
||||
}
|
||||
|
||||
private class StaticType
|
||||
{
|
||||
public static string Static { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should not be able to see static properties.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContainsKey_PropertyStatic_ReturnsFalse()
|
||||
{
|
||||
StaticType t = new StaticType();
|
||||
PropertyDictionary dictionary = new PropertyDictionary(t);
|
||||
bool result = dictionary.ContainsKey("Static");
|
||||
Assert.IsFalse(result, "The property name was found.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keys
|
||||
|
||||
/// <summary>
|
||||
/// Keys should return the name of all of the property names in the object.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestKeys_GetsAllPropertyNames()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 23
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(person);
|
||||
ICollection<string> keys = dictionary.Keys;
|
||||
Assert.AreEqual(2, keys.Count, "The wrong number of keys were returned.");
|
||||
Assert.IsTrue(keys.Contains("Name"), "The Name property was not found.");
|
||||
Assert.IsTrue(keys.Contains("Age"), "The Age property was not found.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Remove
|
||||
|
||||
/// <summary>
|
||||
/// Since a property dictionary is just a wrapper around an object, we cannot remove properties from it.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void TestRemove_IDictionary_ThrowsException()
|
||||
{
|
||||
object instance = new object();
|
||||
IDictionary<string, object> dictionary = new PropertyDictionary(instance);
|
||||
dictionary.Remove("Name");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Since a property dictionary is just a wrapper around an object, we cannot remove properties from it.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void TestRemove_ICollection_ThrowsException()
|
||||
{
|
||||
object instance = new object();
|
||||
ICollection<KeyValuePair<string, object>> collection = new PropertyDictionary(instance);
|
||||
collection.Remove(new KeyValuePair<string, object>("Name", "Whatever"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TryGetValue
|
||||
|
||||
/// <summary>
|
||||
/// If we try to get the value for a property that doesn't exist, false should returned and object set to null.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestTryGetValue_NoSuchProperty_ReturnsFalse()
|
||||
{
|
||||
var instance = new { };
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
object value;
|
||||
bool result = dictionary.TryGetValue("Name", out value);
|
||||
Assert.IsFalse(result, "The property should not have been found.");
|
||||
Assert.IsNull(value, "The value should have been null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we try to get the value for a property that doesn't exist, false should returned and object set to null.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestTryGetValue_PropertyExists_ReturnsTrue()
|
||||
{
|
||||
var instance = new
|
||||
{
|
||||
Name = "Test"
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
object value;
|
||||
bool result = dictionary.TryGetValue("Name", out value);
|
||||
Assert.IsTrue(result, "The property should have been found.");
|
||||
Assert.AreEqual(instance.Name, value, "The value should have equaled the wrapped property value.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Values
|
||||
|
||||
/// <summary>
|
||||
/// We should be able to get the value of all of the properties.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestValues_GetsValues()
|
||||
{
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 23
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
ICollection<object> values = dictionary.Values;
|
||||
Assert.AreEqual(2, values.Count, "The wrong number of values were returned.");
|
||||
Assert.IsTrue(values.Contains("Bob"), "The value for Name was not found.");
|
||||
Assert.IsTrue(values.Contains(23), "The value for Age was not found.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Indexer
|
||||
|
||||
/// <summary>
|
||||
/// If we try to retrieve the value for a property that does not exist, an exception
|
||||
/// should be thrown.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(KeyNotFoundException))]
|
||||
public void TestIndexer_Getter_NoSuchProperty_ThrowsException()
|
||||
{
|
||||
object instance = new object();
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
object value = dictionary["Name"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we try to get a value for a property that exists, the value should
|
||||
/// be returned.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestIndexer_Getter_PropertyExists_ReturnsValue()
|
||||
{
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob"
|
||||
};
|
||||
PropertyDictionary dictionary = new PropertyDictionary(instance);
|
||||
object value = dictionary["Name"];
|
||||
Assert.AreSame(instance.Name, value, "The wrong value was returned.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we try to set the value for a property, an exception should be thrown.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void TestIndexer_Setter_ThrowsException()
|
||||
{
|
||||
PropertyDictionary dictionary = new PropertyDictionary(new { Name = 123 });
|
||||
dictionary["Name"] = 123;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clear
|
||||
|
||||
/// <summary>
|
||||
/// Since the dictionary is just a wrapper, Clear will simply throw an exception.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void TestClear_ThrowsException()
|
||||
{
|
||||
object instance = new object();
|
||||
ICollection<KeyValuePair<string, object>> dictionary = new PropertyDictionary(instance);
|
||||
dictionary.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Contains
|
||||
|
||||
/// <summary>
|
||||
/// Contains should find the key/value pair if both the key and value are equal.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContains_Explicit_PairExists_ReturnsTrue()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob"
|
||||
};
|
||||
ICollection<KeyValuePair<string, object>> collection = new PropertyDictionary(person);
|
||||
bool contains = collection.Contains(new KeyValuePair<string, object>("Name", "Bob"));
|
||||
Assert.IsTrue(contains, "Did not find the pair.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains should not find the key/value pair if the keys are not equal.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContains_Explicit_KeyDoesNotMatch_ReturnsFalse()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob"
|
||||
};
|
||||
ICollection<KeyValuePair<string, object>> collection = new PropertyDictionary(person);
|
||||
bool contains = collection.Contains(new KeyValuePair<string, object>("Age", "Bob"));
|
||||
Assert.IsFalse(contains, "The pair should not have been found.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains should not find the key/value pair if the values are not equal.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestContains_Explicit_ValueDoesNotMatch_ReturnsFalse()
|
||||
{
|
||||
var person = new
|
||||
{
|
||||
Name = "Bob"
|
||||
};
|
||||
ICollection<KeyValuePair<string, object>> collection = new PropertyDictionary(person);
|
||||
bool contains = collection.Contains(new KeyValuePair<string, object>("Name", "Sally"));
|
||||
Assert.IsFalse(contains, "The pair should not have been found.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CopyTo
|
||||
|
||||
/// <summary>
|
||||
/// CopyTo should copy the key/value pairs to an array, assuming there is enough room.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestCopyTo_Explicit()
|
||||
{
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 23
|
||||
};
|
||||
ICollection<KeyValuePair<string, object>> collection = new PropertyDictionary(instance);
|
||||
|
||||
KeyValuePair<string, object>[] array = new KeyValuePair<string, object>[collection.Count];
|
||||
int arrayIndex = 0;
|
||||
collection.CopyTo(array, arrayIndex);
|
||||
|
||||
Assert.IsTrue(array.Contains(new KeyValuePair<string, object>("Name", "Bob")), "The name property was not found.");
|
||||
Assert.IsTrue(array.Contains(new KeyValuePair<string, object>("Age", 23)), "The age property was not found.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetEnumerator
|
||||
|
||||
/// <summary>
|
||||
/// All the items should be enumerated in the dictionary.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestGetEnumerator_EnumeratesAllItems()
|
||||
{
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 23
|
||||
};
|
||||
IEnumerable<KeyValuePair<string, object>> dictionary = new PropertyDictionary(instance);
|
||||
|
||||
Assert.IsTrue(enumerate(dictionary).Contains(new KeyValuePair<string, object>("Name", "Bob")), "The first pair was not present.");
|
||||
Assert.IsTrue(enumerate(dictionary).Contains(new KeyValuePair<string, object>("Age", 23)), "The second pair was not present.");
|
||||
}
|
||||
|
||||
private static IEnumerable<T> enumerate<T>(IEnumerable<T> enumerable)
|
||||
{
|
||||
List<T> items = new List<T>();
|
||||
foreach (T item in enumerable)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All the items should be enumerated in the dictionary.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestGetEnumerator_Explicit_EnumeratesAllItems()
|
||||
{
|
||||
var instance = new
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 23
|
||||
};
|
||||
IEnumerable dictionary = new PropertyDictionary(instance);
|
||||
|
||||
Assert.IsTrue(enumerate(dictionary).Cast<KeyValuePair<string, object>>().Contains(new KeyValuePair<string, object>("Name", "Bob")), "The first pair was not present.");
|
||||
Assert.IsTrue(enumerate(dictionary).Cast<KeyValuePair<string, object>>().Contains(new KeyValuePair<string, object>("Age", 23)), "The second pair was not present.");
|
||||
}
|
||||
|
||||
private static IEnumerable enumerate(IEnumerable enumerable)
|
||||
{
|
||||
ArrayList items = new ArrayList();
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>
|
||||
</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{7F607362-0680-4751-B1DC-621219294AE3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>mustache.test</RootNamespace>
|
||||
<AssemblyName>mustache-sharp.test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
|
||||
<Visible>False</Visible>
|
||||
</CodeAnalysisDependentAssemblyPaths>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="FormatterTester.cs" />
|
||||
<Compile Include="PropertyDictionaryTester.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\mustache-sharp\mustache-sharp.csproj">
|
||||
<Project>{D71B378F-A4BA-4263-A4F0-07A49A0C528D}</Project>
|
||||
<Name>mustache-sharp</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<TestLists xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
|
||||
<TestList name="Lists of Tests" id="8c43106b-9dc1-4907-a29f-aa66a61bf5b6">
|
||||
<RunConfiguration id="2bc42439-1bb6-4112-9c20-eca1ffcae064" name="Local" storage="local.testsettings" type="Microsoft.VisualStudio.TestTools.Common.TestRunConfiguration, Microsoft.VisualStudio.QualityTools.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
</TestList>
|
||||
</TestLists>
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class CompoundBuilder : IBuilder
|
||||
{
|
||||
private readonly List<IBuilder> builders;
|
||||
|
||||
public CompoundBuilder()
|
||||
{
|
||||
builders = new List<IBuilder>();
|
||||
}
|
||||
|
||||
public void AddBuilder(IBuilder builder)
|
||||
{
|
||||
builders.Add(builder);
|
||||
}
|
||||
|
||||
public void Build(Scope scope, StringBuilder output, IFormatProvider provider)
|
||||
{
|
||||
foreach (IBuilder builder in builders)
|
||||
{
|
||||
builder.Build(scope, output, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class EachBuilder : IBuilder
|
||||
{
|
||||
private readonly CompoundBuilder builder;
|
||||
|
||||
public EachBuilder()
|
||||
{
|
||||
builder = new CompoundBuilder();
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public CompoundBuilder Builder
|
||||
{
|
||||
get { return builder; }
|
||||
}
|
||||
|
||||
public void Build(Scope scope, StringBuilder output, IFormatProvider provider)
|
||||
{
|
||||
object value = scope.Find(Key);
|
||||
IEnumerable enumerable = value as IEnumerable;
|
||||
if (enumerable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
IDictionary<string, object> lookup = item as IDictionary<string, object>;
|
||||
if (lookup == null)
|
||||
{
|
||||
lookup = new PropertyDictionary(item);
|
||||
}
|
||||
Scope itemScope = scope.CreateChildScope(item);
|
||||
builder.Build(itemScope, output, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using mustache.Properties;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for the generation of a string based on formatted template.
|
||||
/// </summary>
|
||||
public sealed class Formatter
|
||||
{
|
||||
private readonly CompoundBuilder builder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of a Formatter using the given format string.
|
||||
/// </summary>
|
||||
/// <param name="format">The string containing the placeholders to use as a template.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The format string is null.</exception>
|
||||
/// <exception cref="System.FormatException">The format string is invald.</exception>
|
||||
public Formatter(string format)
|
||||
{
|
||||
if (format == null)
|
||||
{
|
||||
throw new ArgumentNullException("format");
|
||||
}
|
||||
builder = new CompoundBuilder();
|
||||
|
||||
List<string> names = new List<string>();
|
||||
const string key = @"[_\w][_\w\d]*";
|
||||
const string compoundKey = key + @"(\." + key + ")*";
|
||||
const string openIfMatch = @"(?<open_if>(#if\s+?" + compoundKey + @"\s*?))";
|
||||
const string elifMatch = @"(?<elif>(#elif\s+?" + compoundKey + @"\s*?))";
|
||||
const string elseMatch = @"(?<else>(#else\s*?))";
|
||||
const string closeIfMatch = @"(?<close_if>(/if\s*?))";
|
||||
const string openEachMatch = @"(?<open_each>(#each\s+?" + compoundKey + @"\s*?))";
|
||||
const string closeEachMatch = @"(?<close_each>(/each\s*?))";
|
||||
const string openWithMatch = @"(?<open_with>(#with\s+?" + compoundKey + @"\s*?))";
|
||||
const string closeWithMatch = @"(?<close_with>(/with\s*?))";
|
||||
const string commentMatch = @"(?<comment>#!.*?)";
|
||||
const string keyMatch = @"((?<key>" + compoundKey + @")(,(?<alignment>(-)?[\d]+))?(:(?<format>.*?))?)";
|
||||
const string match = "{{(" + openIfMatch + "|"
|
||||
+ elifMatch + "|"
|
||||
+ elseMatch + "|"
|
||||
+ closeIfMatch + "|"
|
||||
+ openEachMatch + "|"
|
||||
+ closeEachMatch + "|"
|
||||
+ openWithMatch + "|"
|
||||
+ closeWithMatch + "|"
|
||||
+ commentMatch + "|"
|
||||
+ keyMatch + ")}}";
|
||||
Regex formatFinder = new Regex(match, RegexOptions.Compiled);
|
||||
List<Match> matches = formatFinder.Matches(format).Cast<Match>().ToList();
|
||||
using (IEnumerator<Match> matchEnumerator = matches.GetEnumerator())
|
||||
{
|
||||
Trimmer trimmer = new Trimmer();
|
||||
int formatIndex = buildCompoundBuilder(builder, trimmer, format, 0, matchEnumerator);
|
||||
StaticBuilder trailingBuilder = new StaticBuilder();
|
||||
string value = format.Substring(formatIndex);
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.None, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substitutes the placeholders in the format string with the values found in the object.
|
||||
/// </summary>
|
||||
/// <param name="format">The string containing the placeholders to use as a template.</param>
|
||||
/// <param name="value">The object to use to replace the placeholders.</param>
|
||||
/// <returns>The format string with the placeholders substituted for by the object values.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">The format string is null.</exception>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">A property was not found in the value.</exception>
|
||||
public static string Format(string format, object value)
|
||||
{
|
||||
Formatter formatter = new Formatter(format);
|
||||
return formatter.Format(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substitutes the placeholders in the format string with the values found in the object.
|
||||
/// </summary>
|
||||
/// <param name="provider">The format provider to use -or- null to use the current culture.</param>
|
||||
/// <param name="format">The string containing the placeholders to use as a template.</param>
|
||||
/// <param name="value">The object to use to replace the placeholders.</param>
|
||||
/// <returns>The format string with the placeholders substituted for by the object values.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">The format string is null.</exception>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">A property was not found in the value.</exception>
|
||||
public static string Format(IFormatProvider provider, string format, object value)
|
||||
{
|
||||
Formatter formatter = new Formatter(format);
|
||||
return formatter.Format(provider, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substitutes the placeholders in the format string with the values found in the given object.
|
||||
/// </summary>
|
||||
/// <param name="value">The object to use to replace the placeholders.</param>
|
||||
/// <returns>The format string with the placeholders substituted for by the lookup values.</returns>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">A property was not found in the object.</exception>
|
||||
/// <remarks>A null value will be replaced with an empty string.</remarks>
|
||||
public string Format(object value)
|
||||
{
|
||||
return format(CultureInfo.CurrentCulture, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substitutes the placeholders in the format string with the values found in the given object.
|
||||
/// </summary>
|
||||
/// <param name="provider">The format provider to use -or- null to use the current culture.</param>
|
||||
/// <param name="value">The object to use to replace the placeholders.</param>
|
||||
/// <returns>The format string with the placeholders substituted for by the lookup values.</returns>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">A property was not found in the object.</exception>
|
||||
/// <remarks>A null value will be replaced with an empty string.</remarks>
|
||||
public string Format(IFormatProvider provider, object value)
|
||||
{
|
||||
if (provider == null)
|
||||
{
|
||||
provider = CultureInfo.CurrentCulture;
|
||||
}
|
||||
return format(provider, value);
|
||||
}
|
||||
|
||||
private static int buildCompoundBuilder(CompoundBuilder builder, Trimmer trimmer, string format, int formatIndex, IEnumerator<Match> matches)
|
||||
{
|
||||
while (matches.MoveNext())
|
||||
{
|
||||
Match match = matches.Current;
|
||||
string value = format.Substring(formatIndex, match.Index - formatIndex);
|
||||
formatIndex = match.Index + match.Length;
|
||||
|
||||
Group keyGroup = match.Groups["key"];
|
||||
if (keyGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = true };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
Group alignmentGroup = match.Groups["alignment"];
|
||||
Group formatGroup = match.Groups["format"];
|
||||
KeyBuilder keyBuilder = new KeyBuilder()
|
||||
{
|
||||
Key = keyGroup.Value,
|
||||
Alignment = alignmentGroup.Value,
|
||||
Format = formatGroup.Value,
|
||||
};
|
||||
builder.AddBuilder(keyBuilder);
|
||||
continue;
|
||||
}
|
||||
|
||||
Group openIfGroup = match.Groups["open_if"];
|
||||
if (openIfGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Header, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
IfBuilder ifBuilder = new IfBuilder();
|
||||
ifBuilder.Key = openIfGroup.Value.Substring(4).Trim();
|
||||
formatIndex = buildIfBuilder(ifBuilder, true, trimmer, format, formatIndex, matches);
|
||||
builder.AddBuilder(ifBuilder);
|
||||
continue;
|
||||
}
|
||||
|
||||
Group openEachGroup = match.Groups["open_each"];
|
||||
if (openEachGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Header, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
EachBuilder eachBuilder = new EachBuilder();
|
||||
eachBuilder.Key = openEachGroup.Value.Substring(6).Trim();
|
||||
formatIndex = buildEachBuilder(eachBuilder, trimmer, format, formatIndex, matches);
|
||||
builder.AddBuilder(eachBuilder);
|
||||
continue;
|
||||
}
|
||||
|
||||
Group openWithGroup = match.Groups["open_with"];
|
||||
if (openWithGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Header, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
WithBuilder withBuilder = new WithBuilder();
|
||||
withBuilder.Key = openWithGroup.Value.Substring(6).Trim();
|
||||
formatIndex = buildWithBuilder(withBuilder, trimmer, format, formatIndex, matches);
|
||||
builder.AddBuilder(withBuilder);
|
||||
continue;
|
||||
}
|
||||
|
||||
Group commentGroup = match.Groups["comment"];
|
||||
if (commentGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
continue;
|
||||
}
|
||||
|
||||
Group elifGroup = match.Groups["elif"];
|
||||
if (elifGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
break;
|
||||
}
|
||||
|
||||
Group elseGroup = match.Groups["else"];
|
||||
if (elseGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Singleton, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
break;
|
||||
}
|
||||
|
||||
Group closeIfGroup = match.Groups["close_if"];
|
||||
if (closeIfGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Footer, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
break;
|
||||
}
|
||||
|
||||
Group closeEachGroup = match.Groups["close_each"];
|
||||
if (closeEachGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Footer, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
break;
|
||||
}
|
||||
|
||||
Group closeWithGroup = match.Groups["close_with"];
|
||||
if (closeWithGroup.Success)
|
||||
{
|
||||
TagAttributes attributes = new TagAttributes() { Type = TagType.Footer, IsOutput = false };
|
||||
trimmer.AddStaticBuilder(builder, attributes, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return formatIndex;
|
||||
}
|
||||
|
||||
private static int buildIfBuilder(IfBuilder builder, bool expectClosingTag, Trimmer trimmer, string format, int formatIndex, IEnumerator<Match> matches)
|
||||
{
|
||||
formatIndex = buildCompoundBuilder(builder.TrueBuilder, trimmer, format, formatIndex, matches);
|
||||
Match match = matches.Current;
|
||||
if (match != null)
|
||||
{
|
||||
Group elifGroup = match.Groups["elif"];
|
||||
if (elifGroup.Success)
|
||||
{
|
||||
IfBuilder elifBuilder = new IfBuilder();
|
||||
elifBuilder.Key = elifGroup.Value.Substring(6).Trim();
|
||||
formatIndex = buildIfBuilder(elifBuilder, false, trimmer, format, formatIndex, matches);
|
||||
builder.FalseBuilder.AddBuilder(elifBuilder);
|
||||
}
|
||||
else
|
||||
{
|
||||
Group elseGroup = match.Groups["else"];
|
||||
if (elseGroup.Success)
|
||||
{
|
||||
formatIndex = buildCompoundBuilder(builder.FalseBuilder, trimmer, format, formatIndex, matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expectClosingTag)
|
||||
{
|
||||
Match closingMatch = matches.Current;
|
||||
checkClosingTag(closingMatch, "close_if", "if");
|
||||
}
|
||||
return formatIndex;
|
||||
}
|
||||
|
||||
private static int buildEachBuilder(EachBuilder builder, Trimmer trimmer, string format, int formatIndex, IEnumerator<Match> matches)
|
||||
{
|
||||
formatIndex = buildCompoundBuilder(builder.Builder, trimmer, format, formatIndex, matches);
|
||||
Match closingMatch = matches.Current;
|
||||
checkClosingTag(closingMatch, "close_each", "each");
|
||||
return formatIndex;
|
||||
}
|
||||
|
||||
private static int buildWithBuilder(WithBuilder builder, Trimmer trimmer, string format, int formatIndex, IEnumerator<Match> matches)
|
||||
{
|
||||
formatIndex = buildCompoundBuilder(builder.Builder, trimmer, format, formatIndex, matches);
|
||||
Match closingMatch = matches.Current;
|
||||
checkClosingTag(closingMatch, "close_with", "with");
|
||||
return formatIndex;
|
||||
}
|
||||
|
||||
private static void checkClosingTag(Match match, string expectedTag, string openingTag)
|
||||
{
|
||||
if (match == null || !match.Groups[expectedTag].Success)
|
||||
{
|
||||
string errorMessage = String.Format(CultureInfo.CurrentCulture, Resources.MissingClosingTag, openingTag);
|
||||
throw new FormatException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private string format(IFormatProvider provider, object topLevel)
|
||||
{
|
||||
Scope scope = new Scope(topLevel);
|
||||
StringBuilder output = new StringBuilder();
|
||||
builder.Build(scope, output, provider);
|
||||
return output.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal interface IBuilder
|
||||
{
|
||||
void Build(Scope scope, StringBuilder output, IFormatProvider provider);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class IfBuilder : IBuilder
|
||||
{
|
||||
private readonly CompoundBuilder trueBuilder;
|
||||
private readonly CompoundBuilder falseBuilder;
|
||||
|
||||
public IfBuilder()
|
||||
{
|
||||
trueBuilder = new CompoundBuilder();
|
||||
falseBuilder = new CompoundBuilder();
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public CompoundBuilder TrueBuilder
|
||||
{
|
||||
get { return trueBuilder; }
|
||||
}
|
||||
|
||||
public CompoundBuilder FalseBuilder
|
||||
{
|
||||
get { return falseBuilder; }
|
||||
}
|
||||
|
||||
public void Build(Scope scope, StringBuilder output, IFormatProvider provider)
|
||||
{
|
||||
object value = scope.Find(Key);
|
||||
bool truthyness = getTruthyness(value);
|
||||
if (truthyness)
|
||||
{
|
||||
trueBuilder.Build(scope, output, provider);
|
||||
}
|
||||
else
|
||||
{
|
||||
falseBuilder.Build(scope, output, provider);
|
||||
}
|
||||
}
|
||||
|
||||
private bool getTruthyness(object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IEnumerable enumerable = value as IEnumerable;
|
||||
if (enumerable != null)
|
||||
{
|
||||
return enumerable.Cast<object>().Any();
|
||||
}
|
||||
if (value is Char)
|
||||
{
|
||||
return (Char)value != '\0';
|
||||
}
|
||||
try
|
||||
{
|
||||
decimal number = (decimal)Convert.ChangeType(value, typeof(decimal));
|
||||
return number != 0.0m;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class KeyBuilder : IBuilder
|
||||
{
|
||||
public KeyBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Alignment
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Format
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Build(Scope scope, StringBuilder output, IFormatProvider provider)
|
||||
{
|
||||
object value = scope.Find(Key);
|
||||
StringBuilder format = new StringBuilder();
|
||||
format.Append("{");
|
||||
format.Append("0");
|
||||
if (!String.IsNullOrWhiteSpace(Alignment))
|
||||
{
|
||||
format.Append(",");
|
||||
format.Append(Alignment);
|
||||
}
|
||||
if (!String.IsNullOrWhiteSpace(Format))
|
||||
{
|
||||
format.Append(":");
|
||||
format.Append(Format);
|
||||
}
|
||||
format.Append("}");
|
||||
output.AppendFormat(provider, format.ToString(), value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("mustache-sharp")]
|
||||
[assembly: AssemblyDescription("A extension of the mustache text template engine for .NET.")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("truncon")]
|
||||
[assembly: AssemblyProduct("mustache-sharp")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2013")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: CLSCompliant(true)]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("e5a4263d-d450-4d85-a4d5-44c0a2822668")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.0.0.0")]
|
||||
[assembly: InternalsVisibleTo("mustache-sharp.test")]
|
|
@ -0,0 +1,81 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.296
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace mustache.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("mustache.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A key or property was not found with the given name..
|
||||
/// </summary>
|
||||
internal static string KeyNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("KeyNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A matching closing tag was not found for the {0} tag..
|
||||
/// </summary>
|
||||
internal static string MissingClosingTag {
|
||||
get {
|
||||
return ResourceManager.GetString("MissingClosingTag", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="KeyNotFound" xml:space="preserve">
|
||||
<value>A key or property was not found with the given name.</value>
|
||||
</data>
|
||||
<data name="MissingClosingTag" xml:space="preserve">
|
||||
<value>A matching closing tag was not found for the {0} tag.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,237 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for creating instances of PropertyDictionary.
|
||||
/// </summary>
|
||||
internal sealed class PropertyDictionary : IDictionary<string, object>
|
||||
{
|
||||
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _cache = new Dictionary<Type,Dictionary<string,PropertyInfo>>();
|
||||
|
||||
private readonly object _instance;
|
||||
private readonly Dictionary<string, PropertyInfo> _typeCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of a PropertyDictionary.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to wrap in the PropertyDictionary.</param>
|
||||
public PropertyDictionary(object instance)
|
||||
{
|
||||
_instance = instance;
|
||||
if (instance == null)
|
||||
{
|
||||
_typeCache = new Dictionary<string, PropertyInfo>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_typeCache = getCacheType(_instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyInfo> getCacheType(object instance)
|
||||
{
|
||||
Type type = instance.GetType();
|
||||
Dictionary<string, PropertyInfo> typeCache;
|
||||
if (!_cache.TryGetValue(type, out typeCache))
|
||||
{
|
||||
typeCache = new Dictionary<string, PropertyInfo>();
|
||||
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;
|
||||
foreach (PropertyInfo propertyInfo in type.GetProperties(flags))
|
||||
{
|
||||
if (!propertyInfo.IsSpecialName)
|
||||
{
|
||||
typeCache.Add(propertyInfo.Name, propertyInfo);
|
||||
}
|
||||
}
|
||||
_cache.Add(type, typeCache);
|
||||
}
|
||||
return typeCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying instance.
|
||||
/// </summary>
|
||||
public object Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
void IDictionary<string, object>.Add(string key, object value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a property with the given name exists.
|
||||
/// </summary>
|
||||
/// <param name="key">The name of the property.</param>
|
||||
/// <returns>True if the property exists; otherwise, false.</returns>
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _typeCache.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the properties in the type.
|
||||
/// </summary>
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get { return _typeCache.Keys; }
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
bool IDictionary<string, object>.Remove(string key)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the value for the given property name.
|
||||
/// </summary>
|
||||
/// <param name="key">The name of the property to get the value for.</param>
|
||||
/// <param name="value">The variable to store the value of the property or the default value if the property is not found.</param>
|
||||
/// <returns>True if a property with the given name is found; otherwise, false.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">The name of the property was null.</exception>
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
PropertyInfo propertyInfo;
|
||||
if (!_typeCache.TryGetValue(key, out propertyInfo))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
value = getValue(propertyInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values of all of the properties in the object.
|
||||
/// </summary>
|
||||
public ICollection<object> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
ICollection<PropertyInfo> propertyInfos = _typeCache.Values;
|
||||
List<object> values = new List<object>();
|
||||
foreach (PropertyInfo propertyInfo in propertyInfos)
|
||||
{
|
||||
object value = getValue(propertyInfo);
|
||||
values.Add(value);
|
||||
}
|
||||
return values.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the property with the given name.
|
||||
/// </summary>
|
||||
/// <param name="key">The name of the property to get or set.</param>
|
||||
/// <returns>The value of the property with the given name.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">The property name was null.</exception>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">The type does not have a property with the given name.</exception>
|
||||
/// <exception cref="System.ArgumentException">The property did not support getting or setting.</exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// The object does not match the target type, or a property is a value type but the value is null.
|
||||
/// </exception>
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
PropertyInfo propertyInfo = _typeCache[key];
|
||||
return getValue(propertyInfo);
|
||||
}
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
void ICollection<KeyValuePair<string, object>>.Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
PropertyInfo propertyInfo;
|
||||
if (!_typeCache.TryGetValue(item.Key, out propertyInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
object value = getValue(propertyInfo);
|
||||
return Equals(item.Value, value);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
List<KeyValuePair<string, object>> pairs = new List<KeyValuePair<string, object>>();
|
||||
ICollection<KeyValuePair<string, PropertyInfo>> collection = _typeCache;
|
||||
foreach (KeyValuePair<string, PropertyInfo> pair in collection)
|
||||
{
|
||||
PropertyInfo propertyInfo = pair.Value;
|
||||
object value = getValue(propertyInfo);
|
||||
pairs.Add(new KeyValuePair<string, object>(pair.Key, value));
|
||||
}
|
||||
pairs.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of properties in the type.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return _typeCache.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether updates will be ignored.
|
||||
/// </summary>
|
||||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the propety name/value pairs in the object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
foreach (KeyValuePair<string, PropertyInfo> pair in _typeCache)
|
||||
{
|
||||
object value = getValue(pair.Value);
|
||||
yield return new KeyValuePair<string, object>(pair.Key, value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private object getValue(PropertyInfo propertyInfo)
|
||||
{
|
||||
return propertyInfo.GetValue(_instance, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mustache.Properties;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class Scope
|
||||
{
|
||||
private readonly object topLevel;
|
||||
private Scope parent;
|
||||
|
||||
public Scope(object topLevel)
|
||||
{
|
||||
parent = null;
|
||||
this.topLevel = topLevel;
|
||||
}
|
||||
|
||||
public Scope CreateChildScope(object topLevel)
|
||||
{
|
||||
Scope scope = new Scope(topLevel);
|
||||
scope.parent = this;
|
||||
return scope;
|
||||
}
|
||||
|
||||
public object Find(string name)
|
||||
{
|
||||
string[] names = name.Split('.');
|
||||
string member = names[0];
|
||||
object nextLevel = topLevel;
|
||||
if (member != "this")
|
||||
{
|
||||
nextLevel = find(member);
|
||||
}
|
||||
for (int index = 1; index < names.Length; ++index)
|
||||
{
|
||||
IDictionary<string, object> context = toLookup(nextLevel);
|
||||
member = names[index];
|
||||
nextLevel = context[member];
|
||||
}
|
||||
return nextLevel;
|
||||
}
|
||||
|
||||
private object find(string name)
|
||||
{
|
||||
IDictionary<string, object> lookup = toLookup(topLevel);
|
||||
if (lookup.ContainsKey(name))
|
||||
{
|
||||
return lookup[name];
|
||||
}
|
||||
if (parent == null)
|
||||
{
|
||||
string message = String.Format(Resources.KeyNotFound, name);
|
||||
throw new KeyNotFoundException(message);
|
||||
}
|
||||
return parent.find(name);
|
||||
}
|
||||
|
||||
private static IDictionary<string, object> toLookup(object value)
|
||||
{
|
||||
IDictionary<string, object> lookup = value as IDictionary<string, object>;
|
||||
if (lookup == null)
|
||||
{
|
||||
lookup = new PropertyDictionary(value);
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class StaticBuilder : IBuilder
|
||||
{
|
||||
public StaticBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Build(Scope scope, StringBuilder output, IFormatProvider provider)
|
||||
{
|
||||
output.Append(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class TagAttributes
|
||||
{
|
||||
public TagAttributes()
|
||||
{
|
||||
}
|
||||
|
||||
public TagType Type
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsOutput
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal enum TagType
|
||||
{
|
||||
None,
|
||||
Singleton,
|
||||
Header,
|
||||
Footer,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class Trimmer
|
||||
{
|
||||
private bool hasHeader;
|
||||
private bool hasFooter;
|
||||
private bool hasTag;
|
||||
private bool canTrim;
|
||||
|
||||
public Trimmer()
|
||||
{
|
||||
hasTag = false;
|
||||
canTrim = true;
|
||||
}
|
||||
|
||||
public void AddStaticBuilder(CompoundBuilder builder, TagAttributes attributes, string value)
|
||||
{
|
||||
string trimmed = value;
|
||||
int newline = value.IndexOf(Environment.NewLine);
|
||||
if (newline == -1)
|
||||
{
|
||||
canTrim &= String.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// finish processing the previous line
|
||||
if (canTrim && hasTag && (!hasHeader || !hasFooter))
|
||||
{
|
||||
string lineEnd = trimmed.Substring(0, newline);
|
||||
if (String.IsNullOrWhiteSpace(lineEnd))
|
||||
{
|
||||
trimmed = trimmed.Substring(newline + Environment.NewLine.Length);
|
||||
}
|
||||
}
|
||||
// start processing the next line
|
||||
hasTag = false;
|
||||
hasHeader = false;
|
||||
hasFooter = false;
|
||||
int lastNewline = value.LastIndexOf(Environment.NewLine);
|
||||
string lineStart = value.Substring(lastNewline + Environment.NewLine.Length);
|
||||
canTrim = String.IsNullOrWhiteSpace(lineStart);
|
||||
}
|
||||
hasTag |= attributes.Type != TagType.None;
|
||||
hasHeader |= attributes.Type == TagType.Header;
|
||||
hasFooter |= hasHeader && attributes.Type == TagType.Footer;
|
||||
canTrim &= !attributes.IsOutput;
|
||||
if (trimmed.Length > 0)
|
||||
{
|
||||
StaticBuilder leading = new StaticBuilder();
|
||||
leading.Value = trimmed;
|
||||
builder.AddBuilder(leading);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace mustache
|
||||
{
|
||||
internal sealed class WithBuilder : IBuilder
|
||||
{
|
||||
private readonly CompoundBuilder builder;
|
||||
|
||||
public WithBuilder()
|
||||
{
|
||||
builder = new CompoundBuilder();
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public CompoundBuilder Builder
|
||||
{
|
||||
get { return builder; }
|
||||
}
|
||||
|
||||
public void Build(Scope scope, StringBuilder output, IFormatProvider provider)
|
||||
{
|
||||
object value = scope.Find(Key);
|
||||
Scope valueScope = scope.CreateChildScope(value);
|
||||
builder.Build(valueScope, output, provider);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{D71B378F-A4BA-4263-A4F0-07A49A0C528D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>mustache</RootNamespace>
|
||||
<AssemblyName>mustache-sharp</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CompoundBuilder.cs" />
|
||||
<Compile Include="EachBuilder.cs" />
|
||||
<Compile Include="Formatter.cs" />
|
||||
<Compile Include="IBuilder.cs" />
|
||||
<Compile Include="IfBuilder.cs" />
|
||||
<Compile Include="KeyBuilder.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="PropertyDictionary.cs" />
|
||||
<Compile Include="Scope.cs" />
|
||||
<Compile Include="StaticBuilder.cs" />
|
||||
<Compile Include="TagAttributes.cs" />
|
||||
<Compile Include="TagType.cs" />
|
||||
<Compile Include="Trimmer.cs" />
|
||||
<Compile Include="WithBuilder.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
Loading…
Reference in New Issue