From e2fd882ef108b6277dcb331814dfaf40ce640a04 Mon Sep 17 00:00:00 2001 From: Travis Parks Date: Tue, 23 Jul 2013 08:44:48 -0400 Subject: [PATCH] Force newlines to be explicit Since there weren't consistent rules for when to include newlines, I decided to make an explicit tag (similar to HTML's
tag). This can have a dramatic impact on your existing code (unless it is just HTML). --- mustache-sharp.test/FormatCompilerTester.cs | 66 ++++---- .../Properties/AssemblyInfo.cs | 4 +- mustache-sharp/CompoundGenerator.cs | 25 +-- mustache-sharp/FormatCompiler.cs | 21 ++- mustache-sharp/NewlineTagDefinition.cs | 31 ++++ mustache-sharp/Properties/AssemblyInfo.cs | 4 +- mustache-sharp/StaticGenerator.cs | 29 +--- mustache-sharp/Trimmer.cs | 147 ------------------ mustache-sharp/mustache-sharp.csproj | 2 +- 9 files changed, 91 insertions(+), 238 deletions(-) create mode 100644 mustache-sharp/NewlineTagDefinition.cs delete mode 100644 mustache-sharp/Trimmer.cs diff --git a/mustache-sharp.test/FormatCompilerTester.cs b/mustache-sharp.test/FormatCompilerTester.cs index f11dd1f..694bb66 100644 --- a/mustache-sharp.test/FormatCompilerTester.cs +++ b/mustache-sharp.test/FormatCompilerTester.cs @@ -60,11 +60,14 @@ namespace Mustache.Test public void TestCompile_OutputNewLineBlank_PrintsBothLines() { FormatCompiler compiler = new FormatCompiler(); - const string format = @"Hello + const string format = @"Hello{{#newline}} + "; + + const string expected = @"Hello "; Generator generator = compiler.Compile(format); string result = generator.Render(null); - Assert.AreEqual(format, result, "The wrong text was generated."); + Assert.AreEqual(expected, result, "The wrong text was generated."); } #endregion @@ -268,7 +271,7 @@ namespace Mustache.Test public void TestCompile_OutputNewLineOutput_PrintsBothLines() { FormatCompiler compiler = new FormatCompiler(); - const string format = @"{{this}} + const string format = @"{{this}}{{#newline}} After"; Generator generator = compiler.Compile(format); string result = generator.Render("Content"); @@ -285,7 +288,7 @@ After"; public void TestCompile_EmptyNewLineKey_PrintsBothLines() { FormatCompiler compiler = new FormatCompiler(); - const string format = @" + const string format = @"{{#newline}} {{this}}"; Generator generator = compiler.Compile(format); string result = generator.Render("Content"); @@ -318,7 +321,7 @@ Content"; public void TestCompile_KeyKey_PrintsBothLines() { FormatCompiler compiler = new FormatCompiler(); - const string format = @"{{this}} + const string format = @"{{this}}{{#newline}} {{this}}"; Generator generator = compiler.Compile(format); string result = generator.Render("Content"); @@ -432,7 +435,7 @@ Content"; const string format = "{{#! comment }} {{#! comment }}"; Generator generator = compiler.Compile(format); string result = generator.Render(new object()); - Assert.AreEqual(String.Empty, result, "The wrong text was generated."); + Assert.AreEqual(" ", result, "The wrong text was generated."); } /// @@ -465,12 +468,12 @@ Content"; /// If a comment makes up the entire format string, the nothing should be printed out. /// [TestMethod] - public void TestCompile_CommentAloneOnlyLine__PrintsEmpty() + public void TestCompile_CommentAloneOnlyLine_PrintsSurroundingSpace() { FormatCompiler compiler = new FormatCompiler(); Generator generator = compiler.Compile(" {{#! comment }} "); string result = generator.Render(null); - Assert.AreEqual(String.Empty, result, "The wrong text was generated."); + Assert.AreEqual(" ", result, "The wrong text was generated."); } /// @@ -486,8 +489,7 @@ Content"; After"; Generator generator = compiler.Compile(format); string result = generator.Render(new object()); - const string expected = @"Before -After"; + const string expected = @"Before After"; Assert.AreEqual(expected, result, "The wrong text was generated."); } @@ -504,8 +506,7 @@ After"; After"; Generator generator = compiler.Compile(format); string result = generator.Render(new object()); - const string expected = @"Before -After"; + const string expected = @"Before After"; Assert.AreEqual(expected, result, "The wrong text was generated."); } @@ -520,14 +521,13 @@ After"; const string format = @"Before {{#! This is a comment }} {{#! This is another comment }} - + {{#newline}} {{#! This is the final comment }} After"; Generator generator = compiler.Compile(format); string result = generator.Render(new object()); - const string expected = @"Before - -After"; + const string expected = @"Before + After"; Assert.AreEqual(expected, result, "The wrong text was generated."); } @@ -543,9 +543,7 @@ After"; After"; Generator generator = compiler.Compile(format); string result = generator.Render(new object()); - const string expected = @"Before - Extra -After"; + const string expected = @"Before ExtraAfter"; Assert.AreEqual(expected, result, "The wrong text was generated."); } @@ -561,7 +559,7 @@ After"; "; Generator generator = compiler.Compile(format); string result = generator.Render(null); - Assert.AreEqual(" ", result, "The wrong text was generated."); + Assert.AreEqual(" ", result, "The wrong text was generated."); } /// @@ -576,7 +574,7 @@ After"; After"; Generator generator = compiler.Compile(format); string result = generator.Render(null); - Assert.AreEqual("After", result, "The wrong text was generated."); + Assert.AreEqual(" After", result, "The wrong text was generated."); } /// @@ -618,8 +616,8 @@ First public void TestCompile_ContentNewLineCommentNewLineContentNewLineComment_PrintsContent() { FormatCompiler compiler = new FormatCompiler(); - const string format = @"First - {{#! comment }} + const string format = @"First{{#newline}} +{{#! comment }} Middle {{#! comment }}"; Generator generator = compiler.Compile(format); @@ -795,6 +793,7 @@ Content{{/if}}"; const string format = @"{{#if this}} First {{/if}}{{#if this}} +{{#newline}} Last {{/if}}"; Generator generator = parser.Compile(format); @@ -832,6 +831,7 @@ Content const string format = @"{{#if this}} {{/if}} First +{{#newline}} {{#if this}} {{/if}} Last"; @@ -1005,10 +1005,11 @@ Last"; } /// - /// + /// A bug was found where the index tag was trying to read the arguments for the next tag. + /// This was caused by the index tag chewing up more of the input than it was supposed to. /// [TestMethod] - public void TestCompile_Each_LoopOverCollectionTwice() + public void TestCompile_Each_ContextAfterIndexTag() { List objects = new List(); objects.Add(new TestObject { Name = "name1", Val = "val1" }); @@ -1016,11 +1017,10 @@ Last"; objects.Add(new TestObject { Name = "name3", Val = "val3" }); const string template = @"{{#each this}} -Item Number: {{#index}}
+Item Number: {{#index}}
{{#newline}} {{/each}} {{#each this}} -Item Number: foo
- +Item Number: foo
{{#newline}} {{/each}}"; FormatCompiler compiler = new FormatCompiler(); @@ -1111,15 +1111,17 @@ Item Number: foo
{ FormatCompiler compiler = new FormatCompiler(); const string format = @"Hello {{Customer.FirstName}}: - +{{#newline}} +{{#newline}} {{#with Order}} {{#if LineItems}} Below are your order details: - +{{#newline}} +{{#newline}} {{#each LineItems}} - {{Name}}: {{UnitPrice:C}} x {{Quantity}} + {{Name}}: {{UnitPrice:C}} x {{Quantity}}{{#newline}} {{/each}} - +{{#newline}} Your order total was: {{Total:C}} {{/if}} {{/with}}"; diff --git a/mustache-sharp.test/Properties/AssemblyInfo.cs b/mustache-sharp.test/Properties/AssemblyInfo.cs index f03d8b1..972b946 100644 --- a/mustache-sharp.test/Properties/AssemblyInfo.cs +++ b/mustache-sharp.test/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("0.1.3.0")] -[assembly: AssemblyFileVersion("0.1.3.0")] +[assembly: AssemblyVersion("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2.0.0")] diff --git a/mustache-sharp/CompoundGenerator.cs b/mustache-sharp/CompoundGenerator.cs index 6e8934c..8cad303 100644 --- a/mustache-sharp/CompoundGenerator.cs +++ b/mustache-sharp/CompoundGenerator.cs @@ -11,7 +11,7 @@ namespace Mustache { private readonly TagDefinition _definition; private readonly ArgumentCollection _arguments; - private readonly LinkedList _primaryGenerators; + private readonly List _primaryGenerators; private IGenerator _subGenerator; /// @@ -23,7 +23,7 @@ namespace Mustache { _definition = definition; _arguments = arguments; - _primaryGenerators = new LinkedList(); + _primaryGenerators = new List(); } /// @@ -47,19 +47,6 @@ namespace Mustache addGenerator(generator, isSubGenerator); } - /// - /// Creates a StaticGenerator from the given value and adds it. - /// - /// The static generators to add. - public void AddStaticGenerators(IEnumerable generators) - { - foreach (StaticGenerator generator in generators) - { - LinkedListNode node = _primaryGenerators.AddLast(generator); - generator.Node = node; - } - } - private void addGenerator(IGenerator generator, bool isSubGenerator) { if (isSubGenerator) @@ -68,7 +55,7 @@ namespace Mustache } else { - _primaryGenerators.AddLast(generator); + _primaryGenerators.Add(generator); } } @@ -76,17 +63,17 @@ namespace Mustache { Dictionary arguments = _arguments.GetArguments(scope); IEnumerable contexts = _definition.GetChildContext(writer, scope, arguments); - LinkedList generators; + List generators; if (_definition.ShouldGeneratePrimaryGroup(arguments)) { generators = _primaryGenerators; } else { - generators = new LinkedList(); + generators = new List(); if (_subGenerator != null) { - generators.AddLast(_subGenerator); + generators.Add(_subGenerator); } } foreach (NestedContext context in contexts) diff --git a/mustache-sharp/FormatCompiler.cs b/mustache-sharp/FormatCompiler.cs index 856e8cf..5593fbd 100644 --- a/mustache-sharp/FormatCompiler.cs +++ b/mustache-sharp/FormatCompiler.cs @@ -37,6 +37,8 @@ namespace Mustache _tagLookup.Add(indexDefinition.Name, indexDefinition); WithTagDefinition withDefinition = new WithTagDefinition(); _tagLookup.Add(withDefinition.Name, withDefinition); + NewlineTagDefinition newlineDefinition = new NewlineTagDefinition(); + _tagLookup.Add(newlineDefinition.Name, newlineDefinition); } /// @@ -75,12 +77,10 @@ namespace Mustache throw new ArgumentNullException("format"); } CompoundGenerator generator = new CompoundGenerator(_masterDefinition, new ArgumentCollection()); - Trimmer trimmer = new Trimmer(); List context = new List() { new Context(_masterDefinition.Name, new ContextParameter[0]) }; - int formatIndex = buildCompoundGenerator(_masterDefinition, context, generator, trimmer, format, 0); + int formatIndex = buildCompoundGenerator(_masterDefinition, context, generator, format, 0); string trailing = format.Substring(formatIndex); - generator.AddStaticGenerators(trimmer.RecordText(trailing, false, false)); - trimmer.Trim(); + generator.AddGenerator(new StaticGenerator(trailing)); return new Generator(generator); } @@ -171,7 +171,6 @@ namespace Mustache TagDefinition tagDefinition, List context, CompoundGenerator generator, - Trimmer trimmer, string format, int formatIndex) { while (true) @@ -192,7 +191,7 @@ namespace Mustache if (match.Groups["key"].Success) { - generator.AddStaticGenerators(trimmer.RecordText(leading, true, true)); + generator.AddGenerator(new StaticGenerator(leading)); formatIndex = match.Index + match.Length; string key = match.Groups["key"].Value; string alignment = match.Groups["alignment"].Value; @@ -217,7 +216,7 @@ namespace Mustache } if (nextDefinition.HasContent) { - generator.AddStaticGenerators(trimmer.RecordText(leading, true, false)); + generator.AddGenerator(new StaticGenerator(leading)); ArgumentCollection arguments = getArguments(nextDefinition, match); CompoundGenerator compoundGenerator = new CompoundGenerator(nextDefinition, arguments); IEnumerable contextParameters = nextDefinition.GetChildContextParameters(); @@ -227,7 +226,7 @@ namespace Mustache ContextParameter[] parameters = contextParameters.Select(p => new ContextParameter(p.Name, arguments.GetKey(p))).ToArray(); context.Add(new Context(nextDefinition.Name, parameters)); } - formatIndex = buildCompoundGenerator(nextDefinition, context, compoundGenerator, trimmer, format, formatIndex); + formatIndex = buildCompoundGenerator(nextDefinition, context, compoundGenerator, format, formatIndex); generator.AddGenerator(nextDefinition, compoundGenerator); if (hasContext) { @@ -236,7 +235,7 @@ namespace Mustache } else { - generator.AddStaticGenerators(trimmer.RecordText(leading, true, true)); + generator.AddGenerator(new StaticGenerator(leading)); ArgumentCollection arguments = getArguments(nextDefinition, match); InlineGenerator inlineGenerator = new InlineGenerator(nextDefinition, arguments); generator.AddGenerator(inlineGenerator); @@ -244,7 +243,7 @@ namespace Mustache } else if (match.Groups["close"].Success) { - generator.AddStaticGenerators(trimmer.RecordText(leading, true, false)); + generator.AddGenerator(new StaticGenerator(leading)); string tagName = match.Groups["name"].Value; TagDefinition nextDefinition = _tagLookup[tagName]; formatIndex = match.Index; @@ -256,7 +255,7 @@ namespace Mustache } else if (match.Groups["comment"].Success) { - generator.AddStaticGenerators(trimmer.RecordText(leading, true, false)); + generator.AddGenerator(new StaticGenerator(leading)); formatIndex = match.Index + match.Length; } else if (match.Groups["unknown"].Success) diff --git a/mustache-sharp/NewlineTagDefinition.cs b/mustache-sharp/NewlineTagDefinition.cs new file mode 100644 index 0000000..e814fbd --- /dev/null +++ b/mustache-sharp/NewlineTagDefinition.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Mustache +{ + /// + /// Defines a tag that outputs a newline. + /// + internal sealed class NewlineTagDefinition : InlineTagDefinition + { + /// + /// Initializes a new instance of an NewlineTagDefinition. + /// + public NewlineTagDefinition() + : base("newline") + { + } + + /// + /// 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) + { + writer.Write(Environment.NewLine); + } + } +} diff --git a/mustache-sharp/Properties/AssemblyInfo.cs b/mustache-sharp/Properties/AssemblyInfo.cs index 07e6b38..abef423 100644 --- a/mustache-sharp/Properties/AssemblyInfo.cs +++ b/mustache-sharp/Properties/AssemblyInfo.cs @@ -34,6 +34,6 @@ using System.Runtime.CompilerServices; // 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.1.3.0")] -[assembly: AssemblyFileVersion("0.1.3.0")] +[assembly: AssemblyVersion("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2.0.0")] [assembly: InternalsVisibleTo("mustache-sharp.test")] \ No newline at end of file diff --git a/mustache-sharp/StaticGenerator.cs b/mustache-sharp/StaticGenerator.cs index f03fd4a..8ac8996 100644 --- a/mustache-sharp/StaticGenerator.cs +++ b/mustache-sharp/StaticGenerator.cs @@ -9,20 +9,14 @@ namespace Mustache /// internal sealed class StaticGenerator : IGenerator { + private readonly string value; + /// /// Initializes a new instance of a StaticGenerator. /// - public StaticGenerator() + public StaticGenerator(string value) { - } - - /// - /// Gets or sets the linked list node containing the current generator. - /// - public LinkedListNode Node - { - get; - set; + this.value = value.Replace(Environment.NewLine, String.Empty); } /// @@ -30,20 +24,7 @@ namespace Mustache /// public string Value { - get; - set; - } - - /// - /// Removes the static text from the final output. - /// - public void Prune() - { - if (Node != null) - { - Node.List.Remove(Node); - Node = null; - } + get { return value; } } void IGenerator.GetText(KeyScope scope, TextWriter writer, object contextData) diff --git a/mustache-sharp/Trimmer.cs b/mustache-sharp/Trimmer.cs deleted file mode 100644 index b6179fe..0000000 --- a/mustache-sharp/Trimmer.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Mustache -{ - /// - /// Removes unnecessary lines from the final output. - /// - internal sealed class Trimmer - { - private readonly LinkedList _lines; - private LinkedListNode _currentLine; - - /// - /// Initializes a new instance of a Trimmer. - /// - public Trimmer() - { - _lines = new LinkedList(); - _currentLine = _lines.AddLast(new LineDetails()); - } - - /// - /// Updates the state of the trimmer, indicating that the given text was encountered before an inline tag. - /// - /// The text at the end of the format string. - /// The generator created for the inline tag. - /// A static generator containing the passed text. - public IEnumerable RecordText(string value, bool isTag, bool isOutput) - { - int newLineIndex = value.IndexOf(Environment.NewLine); - if (newLineIndex == -1) - { - StaticGenerator generator = new StaticGenerator() { Value = value }; - _currentLine.Value.Generators.Add(generator); - _currentLine.Value.HasTag |= isTag; - _currentLine.Value.HasOutput |= !String.IsNullOrWhiteSpace(value); - yield return generator; - } - else - { - string[] lines = value.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); - - // get the trailing generator - string trailing = lines[0]; - StaticGenerator trailingGenerator = new StaticGenerator() { Value = trailing }; - _currentLine.Value.Generators.Add(trailingGenerator); - _currentLine.Value.HasOutput |= !String.IsNullOrWhiteSpace(trailing); - yield return trailingGenerator; - - // get the middle generators - for (int lineIndex = 1; lineIndex < lines.Length - 1; ++lineIndex) - { - string middle = lines[lineIndex]; - StaticGenerator middleGenerator = new StaticGenerator() { Value = middle }; - LineDetails middleDetails = new LineDetails() { HasTag = false }; - _currentLine = _lines.AddLast(middleDetails); - _currentLine.Value.Generators.Add(middleGenerator); - _currentLine.Value.HasOutput = true; - yield return middleGenerator; - } - - // get the leading generator - string leading = lines[lines.Length - 1]; - StaticGenerator leadingGenerator = new StaticGenerator() { Value = leading }; - LineDetails details = new LineDetails() { HasTag = isTag }; - _currentLine = _lines.AddLast(details); - _currentLine.Value.Generators.Add(leadingGenerator); - _currentLine.Value.HasOutput = !String.IsNullOrWhiteSpace(leading); - yield return leadingGenerator; - } - if (isOutput) - { - _currentLine.Value.HasOutput = true; - } - } - - public void Trim() - { - removeBlankLines(); - separateLines(); - removeEmptyGenerators(); - } - - private void removeBlankLines() - { - LinkedListNode current = _lines.First; - while (current != null) - { - LineDetails details = current.Value; - LinkedListNode temp = current; - current = current.Next; - if (details.HasTag && !details.HasOutput) - { - foreach (StaticGenerator generator in temp.Value.Generators) - { - generator.Prune(); - } - temp.List.Remove(temp); - } - } - } - - private void separateLines() - { - LinkedListNode current = _lines.First; - while (current != _lines.Last) - { - List generators = current.Value.Generators; - StaticGenerator lastGenerator = generators[generators.Count - 1]; - lastGenerator.Value += Environment.NewLine; - current = current.Next; - } - } - - private void removeEmptyGenerators() - { - LinkedListNode current = _lines.First; - while (current != null) - { - foreach (StaticGenerator generator in current.Value.Generators) - { - if (generator.Value.Length == 0) - { - generator.Prune(); - } - } - current = current.Next; - } - } - - private sealed class LineDetails - { - public LineDetails() - { - Generators = new List(); - } - - public bool HasTag { get; set; } - - public List Generators { get; set; } - - public bool HasOutput { get; set; } - } - } -} diff --git a/mustache-sharp/mustache-sharp.csproj b/mustache-sharp/mustache-sharp.csproj index 0d5d7b4..55d74ea 100644 --- a/mustache-sharp/mustache-sharp.csproj +++ b/mustache-sharp/mustache-sharp.csproj @@ -40,6 +40,7 @@ + @@ -68,7 +69,6 @@ -