Beispiel #1
0
 public void Auto_Cascase_Partial_Loaders_On_Add()
 {
     var stubble = new StubbleBuilder()
         .AddToPartialTemplateLoader(new DictionaryLoader(new Dictionary<string, string>
         {
             {"Foo", "I'm Foo"},
             {"Bar", "I'm Bar"}
         })).Build();
     Assert.Equal("I'm Foo", stubble.Render("{{> Foo}}", new { foo = "blah" }));
     Assert.Equal("bar", stubble.Render("{{foo}}", new { foo = "bar" }));
 }
Beispiel #2
0
        public void It_Can_Render_WithPartials_FromLoader()
        {
            var stubble = new StubbleBuilder()
                .SetPartialTemplateLoader(new DictionaryLoader(new Dictionary<string, string>
                {
                    {"foo", "{{Foo}} this"}
                })).Build();

            var output = stubble.Render("{{> foo}}", new { Foo = "Bar" });
            Assert.Equal("Bar this", output);
        }
        public void It_Handles_Arrays_Correctly()
        {
            const string json = "{ foo: [ { bar: \"foobar\" } ] }";

            var stubble = new StubbleBuilder()
                          .Configure(settings => settings.AddJsonNet())
                          .Build();

            var obj = JsonConvert.DeserializeObject(json);

            var output = stubble.Render("{{#foo}}{{bar}}{{/foo}}", obj);

            Assert.NotNull(output);
            Assert.Equal("foobar", output);
        }
        public void Truthy_Checks_Work_For_Inverted()
        {
            const string json = "{ showme: false, foo: { bar: \"foobar\" } }";

            var stubble = new StubbleBuilder()
                          .Configure(settings => settings.AddJsonNet())
                          .Build();

            var obj = JsonConvert.DeserializeObject(json);

            var output = stubble.Render("{{^showme}}{{foo.bar}}{{/showme}}", obj);

            Assert.NotNull(output);
            Assert.Equal("foobar", output);
        }
        public void It_Can_Add_Enumeration_Converters()
        {
            var builder = new StubbleBuilder()
                          .Configure(b =>
            {
                b.AddEnumerationConversion(typeof(NameValueCollection), (obj) => null);
            });

            var settingsBuilder = new RendererSettingsBuilder();

            builder.ConfigureSettings(settingsBuilder);

            Assert.Contains(typeof(NameValueCollection), settingsBuilder.EnumerationConverters.Keys);
            Assert.Null(settingsBuilder.EnumerationConverters[typeof(NameValueCollection)](null));
        }
        public void It_Can_Add_Add_Value_Getters()
        {
            var builder = new StubbleBuilder()
                          .Configure(b =>
            {
                b.AddValueGetter(typeof(string), (o, s, i) => null);
            });

            var settingsBuilder = new RendererSettingsBuilder();

            builder.ConfigureSettings(settingsBuilder);

            Assert.Contains(typeof(string), settingsBuilder.ValueGetters.Keys);
            Assert.Null(settingsBuilder.ValueGetters[typeof(string)](null, null, false));
        }
        public void It_Can_Build_Stubble_Instance()
        {
            var stubble = new StubbleBuilder().Build();

            Assert.NotNull(stubble);
            Assert.NotNull(stubble.Registry.ValueGetters);
            Assert.NotNull(stubble.Registry.TokenGetters);
            Assert.NotNull(stubble.Registry.TokenMatchRegex);
            Assert.NotNull(stubble.Registry.TruthyChecks);
            Assert.True(stubble.Registry.TemplateLoader is StringLoader);
            Assert.False(stubble.Registry.IgnoreCaseOnKeyLookup);
            Assert.Null(stubble.Registry.PartialTemplateLoader);

            Assert.NotEmpty(stubble.Registry.ValueGetters);
            Assert.NotEmpty(stubble.Registry.TokenGetters);
        }
Beispiel #8
0
        public void ItShouldNotRenderHelperWithMissingLookedUpArgumentThatIsntValueType()
        {
            var helpers = new Helpers()
                          .Register <string>("ToCapitalLetters", (context, arg) => arg.ToUpperInvariant());

            var renderer = new StubbleBuilder()
                           .Configure(conf => conf.AddHelpers(helpers))
                           .Build();

            var res = renderer.Render("User name is '{{Name}}' and nickname is '{{Nickname}}'. In capital letters name is '{{ToCapitalLetters Name}}' and nickname is '{{ToCapitalLetters Nickname}}'", new
            {
                Name = "John"
            });

            Assert.Equal("User name is 'John' and nickname is ''. In capital letters name is 'JOHN' and nickname is ''", res);
        }
Beispiel #9
0
        public void It_Can_Render_Dictionary_WithIgnoreCaseTrue()
        {
            var stubble = new StubbleBuilder()
                          .Configure(b =>
            {
                b.SetIgnoreCaseOnKeyLookup(true);
            })
                          .Build();

            var output = stubble.Render("{{foo}}", new Dictionary <string, object>()
            {
                { "Foo", "Bar" }
            });

            Assert.Equal("Bar", output);
        }
Beispiel #10
0
        public void ItShouldRenderHelperWithConstantQuotedStringArgument()
        {
            var helpers = new Helpers()
                          .Register <string>("ToCapitalLetters", (context, arg) => arg.ToUpperInvariant());

            var renderer = new StubbleBuilder()
                           .Configure(conf => conf.AddHelpers(helpers))
                           .Build();

            var res = renderer.Render("User name is '{{Name}}' and nickname is '{{Nickname}}'. In capital letters name is '{{ToCapitalLetters Name}}' and nickname is '{{ToCapitalLetters 'Nickname'}}'", new
            {
                Name = "John"
            });

            Assert.Equal("User name is 'John' and nickname is ''. In capital letters name is 'JOHN' and nickname is 'NICKNAME'", res);
        }
Beispiel #11
0
        public void ItShouldRenderHelperWithTwoConstantArguments()
        {
            var helpers = new Helpers()
                          .Register("ReplaceString", (HelperContext context, string searchString, string oldString, string newString) => searchString?.Replace(oldString, newString, StringComparison.InvariantCulture));

            var renderer = new StubbleBuilder()
                           .Configure(conf => conf.AddHelpers(helpers))
                           .Build();

            var result = renderer.Render("Name: {{ReplaceString Name 'XXX' ' '}}", new
            {
                Name = "JohnXXXSmith"
            });

            Assert.Equal("Name: John Smith", result);
        }
Beispiel #12
0
        public void Auto_Cascase_Partial_Loaders_On_Add()
        {
            var stubble = new StubbleBuilder()
                          .Configure(builder =>
            {
                builder.AddToPartialTemplateLoader(new DictionaryLoader(new Dictionary <string, string>
                {
                    { "Foo", "I'm Foo" },
                    { "Bar", "I'm Bar" }
                }));
            })
                          .Build();

            Assert.Equal("I'm Foo", stubble.Render("{{> Foo}}", new { foo = "blah" }));
            Assert.Equal("bar", stubble.Render("{{foo}}", new { foo = "bar" }));
        }
Beispiel #13
0
        public async Task <Message> Render <TModel>(string subject, string body, TModel data, CancellationToken cancellationToken)
        {
            var stubble = new StubbleBuilder().Build();

            var renderedSubject = subject != null ? await stubble.RenderAsync(subject, data) : null;

            var renderedBody = body != null ? await stubble.RenderAsync(body, data) : null;

            var renderedHtmlBody = renderedBody != null?Markdig.Markdown.ToHtml(renderedBody) : null;

            return(new Message
            {
                Subject = renderedSubject,
                Body = renderedHtmlBody
            });
        }
Beispiel #14
0
        /// <summary>
        /// To generate Code File based on Mustache File and JSON file
        /// </summary>
        /// <param name="fileName"> Code File to be created</param>
        /// <param name="jsonObject">JSON Object which contains all values for parsing Mustache file</param>
        /// <returns></returns>
        public static async Task <string> GenerateCodeFile(string fileName, string codeFilePath, JObject jsonObject)
        {
            var stubble = new StubbleBuilder().Configure(settings => settings.AddJsonNet()).Build();

            string startupMustacheFile = Directory.GetCurrentDirectory() + @"\Templates\AspDotNetCoreTemplate\" + fileName + ".Mustache";

            string codeOutput = string.Empty;

            using (StreamReader streamReader = new StreamReader(startupMustacheFile, Encoding.UTF8))
            {
                codeOutput = stubble.Render(streamReader.ReadToEnd(), jsonObject);

                await File.WriteAllTextAsync(codeFilePath, codeOutput);
            }

            return(codeOutput);
        }
Beispiel #15
0
        public void It_Can_Set_A_Partial_Template_Loader()
        {
            var builder = new StubbleBuilder()
                          .Configure(b =>
            {
                b.SetPartialTemplateLoader(new DictionaryLoader(new Dictionary <string, string> {
                    { "test", "{{foo}}" }
                }));
            });

            var settingsBuilder = new RendererSettingsBuilder();

            builder.ConfigureSettings(settingsBuilder);

            Assert.NotNull(settingsBuilder.PartialTemplateLoader);
            Assert.True(settingsBuilder.PartialTemplateLoader is DictionaryLoader);
        }
        public void VerifyDefaultChangelogIsGenerated()
        {
            var now        = DateTimeOffset.Now;
            var yesterday  = now.AddDays(-1);
            var readerMock = new Mock <IGenericReader <IChange> >();
            var cacheMock  = new Mock <IChangeCache>();
            var renderer   = new StubbleBuilder().Build();
            var readers    = new List <IGenericReader <IChange> >
            {
                readerMock.Object
            };
            var expectedChanges = new List <IChange>
            {
                new DefaultChange(new ChangeVersion("1.0.0"), "Added", "Added some other change", now),
                new DefaultChange(new ChangeVersion("1.0.0"), "Removed", "Removed some other change", now),
                new DefaultChange(new ChangeVersion("0.1.0"), "Added", "Added some change", yesterday),
                new DefaultChange(new ChangeVersion("0.1.0"), "Removed", "Removed some change", yesterday),
            };
            var expectedValueDictionary = ToValueDictionary(expectedChanges);

            readerMock.Setup(r => r.Values()).Returns(expectedChanges);
            cacheMock.Setup(c => c.Add(It.Is <IEnumerable <IChange> >(changes => changes.SequenceEqual(expectedChanges))));
            cacheMock.Setup(c => c.GetAsValueDictionary()).Returns(expectedValueDictionary);

            var expected = $@"
## [1.0.0] - {now:yyyy-MM-dd}
### Added
- Added some other change

### Removed
- Removed some other change

## [0.1.0] - {yesterday:yyyy-MM-dd}
### Added
- Added some change

### Removed
- Removed some change

";

            var generator = new StringChangelogGenerator(readers, cacheMock.Object, Template, renderer);
            var actual    = generator.Generate();

            Assert.That(actual, Is.EqualTo(expected));
        }
Beispiel #17
0
        public async Task It_Should_Skip_Html_Encoding_With_Setting_Async()
        {
            var stubble = new StubbleBuilder()
                          .Build();

            var obj = new
            {
                Html = "<b>Html</b>"
            };

            var result = await stubble.RenderAsync("{{Html}}\n{{{Html}}}", obj, new RenderSettings
            {
                SkipHtmlEncoding = true
            });

            Assert.Equal("<b>Html</b>\n<b>Html</b>", result);
        }
Beispiel #18
0
        /// <summary>
        ///     Creates the output document.
        /// </summary>
        private void CreateOutputDocument()
        {
            var currentDir      = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? ".";
            var contentTemplate = File.ReadAllText(Path.Combine(currentDir, "Templates", "Content.html"));
            var reportTemplate  = File.ReadAllText(Path.Combine(currentDir, "Templates", "Report.html"));

            // Render Content
            var stubble         = new StubbleBuilder().Build();
            var projectsContent = stubble.Render(contentTemplate, new { Projects = CodeProjects });

            // Insert content into report file
            var report    = reportTemplate.Replace("{{PROJECTS}}", projectsContent);
            var directory = new FileInfo(_options.ReportPath).Directory.FullName;

            Directory.CreateDirectory(directory);
            File.WriteAllText(_options.ReportPath, report);
        }
Beispiel #19
0
        public async Task Handle(HttpRequest req, HttpResponse res, object model, CancellationToken cancellationToken)
        {
            var source = viewLocator.GetView(model, res.HttpContext);

            if (string.IsNullOrEmpty(source))
            {
                res.StatusCode  = 500;
                res.ContentType = "text/plain";
                await res.WriteAsync("View not found", cancellationToken);
            }
            var stubble = new StubbleBuilder().Build();

            res.ContentType = "text/html";
            res.StatusCode  = (int)HttpStatusCode.OK;
            var output = await stubble.RenderAsync(source, model);

            await res.WriteAsync(output, cancellationToken);
        }
Beispiel #20
0
        public override Stream GetStream()
        {
            var stubble = new StubbleBuilder()
                          .Configure(settings => {
                settings.SetIgnoreCaseOnKeyLookup(true);
                settings.SetMaxRecursionDepth(512);
            })
                          .Build();

            var encoded = Encoding.ASCII.GetBytes(
                stubble.Render(new StreamReader($"{ProjectPath}/Templates/errors.html", Encoding.UTF8).ReadToEnd(), new {
                Title     = "Error",
                ErrorText = StatusCode.ToString()
            })
                );

            return(new MemoryStream(encoded));
        }
        public async Task <IActionResult> GetHash([FromBody] Dictionary <string, object> rawdata, string template)
        {
            // first do a mustache merge.
            var    stubble  = new StubbleBuilder().Build();
            string filename = $"Templates/{template}.mustache";

            if (System.IO.File.Exists(filename))
            {
                string format = System.IO.File.ReadAllText(filename);
                var    html   = stubble.Render(format, rawdata);

                // compute a hash of the template to render as PDF
                var hash = HashUtility.GetSHA256(Encoding.UTF8.GetBytes(html));
                return(new JsonResult(new { hash = hash }));
            }

            return(new NotFoundResult());
        }
        public async Task <IActionResult> Test(TestTemplateDto template)
        {
            Mail mail;

            try
            {
                mail = context.Set <Mail>()
                       .Where(x => x.User.Id == user.Id && x.Id == template.MailId)
                       .First();
            }
            catch (Exception ex)
            {
                throw new BusinessException("mail-not-found", "The provided mail was not found", ex);
            }

            var stubble  = new StubbleBuilder().Build();
            var from     = new MailAddress(mail.EmailAddress);
            var to       = new MailAddress(template.Recipient.Email);
            var password = encryption.Decrypt(mail.Password, template.Secret);
            var subject  = await stubble.RenderAsync(template.Subject, template.Recipient.Args);

            var body = await stubble.RenderAsync(template.Content, template.Recipient.Args);

            var message = new MailMessage(from, to)
            {
                Subject      = subject,
                Body         = body,
                BodyEncoding = Encoding.UTF8,
                IsBodyHtml   = template.IsHtml
            };

            using var smtp = new SmtpClient(mail.Host, mail.Port)
                  {
                      DeliveryMethod        = SmtpDeliveryMethod.Network,
                      UseDefaultCredentials = false,
                      EnableSsl             = mail.EnableSsl,
                      Credentials           = new NetworkCredential(mail.EmailAddress, password)
                  };

            await smtp.SendMailAsync(message);

            return(Ok());
        }
        public IActionResult GetPDF([FromBody] Dictionary <string, object> rawdata, string template)
        {
            // first do a mustache merge.
            var    stubble  = new StubbleBuilder().Build();
            string filename = $"Templates/{template}.mustache";

            if (System.IO.File.Exists(filename))
            {
                string format = System.IO.File.ReadAllText(filename);
                var    html   = stubble.Render(format, rawdata);

                var doc = new HtmlToPdfDocument()
                {
                    GlobalSettings =
                    {
                        PaperSize   = PaperKind.Letter,
                        Orientation = Orientation.Portrait,
                        Margins     = new MarginSettings(5.0, 5.0, 5.0, 5.0)
                    },

                    Objects =
                    {
                        new ObjectSettings()
                        {
                            HtmlContent = html
                        }
                    }
                };
                try
                {
                    var pdf = _generatePdf.Convert(doc);
                    return(File(pdf, "application/pdf"));
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "ERROR rendering PDF");
                    _logger.LogError(template);
                    _logger.LogError(html);
                }
            }

            return(new NotFoundResult());
        }
Beispiel #24
0
    void travisOverrideTemplates(CustomVersion version, string selectedNamespace = null)
    {
        if (selectedNamespace == null)
        {
            selectedNamespace = K8sNamespace ?? Branch;
        }

        var dataHash = new Dictionary <string, object>()
        {
            { "dockerVer", version.ToDockerTag() },
            { "namespace", selectedNamespace },
        };
        var stubble = new StubbleBuilder().Build();

        using (var streamReader = new StreamReader(TravisTemplateFile, Encoding.UTF8))
        {
            var content = stubble.Render(streamReader.ReadToEnd(), dataHash);
            File.WriteAllText(TravisFile, content);
        }
    }
Beispiel #25
0
        public void It_Adds_To_Composite_Loader_If_One_Is_Defined()
        {
            var builder = new StubbleBuilder()
                          .Configure(b =>
            {
                b.SetTemplateLoader(new CompositeLoader(new DictionaryLoader(new Dictionary <string, string> {
                    { "test", "{{foo}}" }
                })))
                .AddToTemplateLoader(new DictionaryLoader(new Dictionary <string, string> {
                    { "test2", "{{bar}}" }
                }));
            });

            var settingsBuilder = new RendererSettingsBuilder();

            builder.ConfigureSettings(settingsBuilder);

            Assert.NotNull(settingsBuilder.TemplateLoader);
            Assert.True(settingsBuilder.TemplateLoader is CompositeLoader);
        }
Beispiel #26
0
        public async Task It_Should_Allow_An_NonAsync_Render_Function_In_Lambda()
        {
            var stubble = new StubbleBuilder()
                          .Build();

            var obj = new
            {
                Value           = "a",
                ValueDictionary = new Dictionary <string, string>
                {
                    { "a", "A is Cool" },
                    { "b", "B is Cool" },
                },
                ValueRender = new Func <string, Func <string, string>, object>((string text, Func <string, string> render)
                                                                               => "{{ValueDictionary." + render("{{Value}}") + "}}")
            };

            var result = await stubble.RenderAsync("{{#ValueRender}}{{/ValueRender}}", obj);

            Assert.Equal("A is Cool", result);
        }
Beispiel #27
0
        public void It_Should_Allow_A_Render_Function_WithContext_In_Lambda()
        {
            var stubble = new StubbleBuilder()
                          .Build();

            var obj = new
            {
                Value           = "a",
                ValueDictionary = new Dictionary <string, string>
                {
                    { "a", "A is Cool" },
                    { "b", "B is Cool" },
                },
                ValueRender = new Func <dynamic, string, Func <string, string>, object>((dynamic ctx, string text, Func <string, string> render)
                                                                                        => "{{ValueDictionary." + render(ctx.Value) + "}}")
            };

            var result = stubble.Render("{{#ValueRender}}{{/ValueRender}}", obj);

            Assert.Equal("A is Cool", result);
        }
Beispiel #28
0
        public void It_Can_Add_Truthy_Checks()
        {
            var builder = new StubbleBuilder()
                          .Configure(b =>
            {
                b.AddTruthyCheck <string>((val) =>
                {
                    return(val.Equals("Foo"));
                });
            });

            var settingsBuilder = new RendererSettingsBuilder();

            builder.ConfigureSettings(settingsBuilder);

            var check = Assert.Single(settingsBuilder.TruthyChecks);

            Assert.Equal(typeof(string), check.Key);
            Assert.True(check.Value[0]("Foo"));
            Assert.False(check.Value[0]("Bar"));
        }
Beispiel #29
0
        protected override void ProcessRecord()
        {
            var s = new RendererSettingsBuilder().BuildSettings();

            if (SkipHtmlEncoding)
            {
                s.RenderSettings.SkipHtmlEncoding = true;
            }


            var stubble = new StubbleBuilder().Configure(settings => settings.AddJsonNet()).Build();

            var    data   = JsonConvert.DeserializeObject(json);
            string output = stubble.Render(template, data, s.RenderSettings);

            if (useCustomWhiteSpaceClearner)
            {
                output = CustomWhiteSpaceCleaner(output);
            }
            WriteObject(output);
        }
Beispiel #30
0
        public void ItShouldCallHelperWhenExistsStaticAndDynamicVariable(string staticValue)
        {
            var helpers = new Helpers()
                          .Register <string, int>("MyHelper", (context, staticVariable, dynamicVariable) =>
            {
                return($"<{staticVariable}#{dynamicVariable}>");
            });

            var builder = new StubbleBuilder()
                          .Configure(conf =>
            {
                conf.AddHelpers(helpers);
            })
                          .Build();

            var tmpl = @"{{MyHelper " + staticValue + " Count }}";

            var res = builder.Render(tmpl, new { Count = 10 });

            Assert.Equal($"<Count#10>", res);
        }
Beispiel #31
0
        public void HelpersShouldBeAbleToHaveStaticParameterWithEscapedQuotes()
        {
            var helpers = new Helpers()
                          .Register <string, string>("DefaultMe", (context, str, @default) =>
            {
                return(string.IsNullOrEmpty(str) ? @default : str);
            });

            var builder = new StubbleBuilder()
                          .Configure(conf =>
            {
                conf.AddHelpers(helpers);
            })
                          .Build();

            var tmpl = @"{{DefaultMe Value 'I\'m Defaulted'}}";

            var res = builder.Render(tmpl, new { Value = "" });

            Assert.Equal("I'm Defaulted", res);
        }
Beispiel #32
0
        public void HelpersShouldBeAbleToHaveStaticAndDynamicParameters()
        {
            var helpers = new Helpers()
                          .Register <decimal, decimal>("Multiply", (context, count, multiplier) =>
            {
                return($"{count * multiplier}");
            });

            var builder = new StubbleBuilder()
                          .Configure(conf =>
            {
                conf.AddHelpers(helpers);
            })
                          .Build();

            var tmpl = @"{{Multiply 5 5}}, {{Multiply Count 5}}";

            var res = builder.Render(tmpl, new { Count = 2 });

            Assert.Equal("25, 10", res);
        }
        public override Stream GetStream()
        {
            var stubble = new StubbleBuilder()
                          .Configure(settings => {
                settings.SetIgnoreCaseOnKeyLookup(true);
                settings.SetMaxRecursionDepth(512);
            })
                          .Build();

            string backUrl = null;

            if (_filePath != "/")
            {
                backUrl = Path.GetDirectoryName(_filePath);
            }

            var encoded = Encoding.ASCII.GetBytes(
                stubble.Render(new StreamReader($"{ProjectPath}/Templates/main.html", Encoding.UTF8).ReadToEnd(), new {
                IsRoot      = _filePath == "/",
                BackUrl     = backUrl,
                WithListing = true,
                Title       = new DirectoryInfo(_filePath).Name,
                Files       = Directory.GetFiles(_filePath).Select(dir => {
                    dir   = dir.Trim();
                    var a = new Dictionary <string, string>();
                    a.Add("BaseName", new FileInfo(dir).Name);
                    a.Add("FullPath", dir);
                    return(a);
                }).ToList(),
                Directories = Directory.GetDirectories(_filePath).Select(dir => {
                    var a = new Dictionary <string, string>();
                    a.Add("BaseName", new DirectoryInfo(dir).Name);
                    a.Add("FullPath", dir);
                    return(a);
                }).ToList()
            })
                );

            return(new MemoryStream(encoded));
        }
Beispiel #34
0
        public void It_Should_Not_Render_If_Partial_Doesnt_Exist_In_Loader()
        {
            var stubble = new StubbleBuilder()
                  .SetPartialTemplateLoader(new DictionaryLoader(new Dictionary<string, string>
                {
                    {"foo", "{{Foo}} this"}
                })).Build();

            var output = stubble.Render("{{> foo2}}", new { Foo = "Bar" });
            Assert.Equal("", output);
        }
Beispiel #35
0
        public void It_Should_Have_Fresh_Depth_On_Each_Render()
        {
            var stubble = new StubbleBuilder().SetMaxRecursionDepth(128).Build();

            for (var i = 0; i < 256; i++)
            {
                Assert.NotNull(stubble.Render("{{Foo}}", new { Foo = 1 }));
            }
        }
Beispiel #36
0
        public void It_Should_Be_Able_To_Change_Max_Recursion_Depth()
        {
            const string rowTemplate = @"
            <div class='row'>
                {{#content}}
                    {{#is_column}}
                        {{>column}}
                    {{/is_column}}
                {{/content}}
            </div>";

            const string columnTemplate = @"
            <div class='column'>
                {{#content}}
                    {{#is_text}}
                        {{>text}}
                    {{/is_text}}
                    {{#is_row}}
                        {{>row}}
                    {{/is_row}}
                {{/content}}
            </div>";

            const string textTemplate = @"
            <span class='text'>
                {{text}}
            </span>";

            var treeData = new
            {
                is_row = true,
                content = new
                {
                    is_column = true,
                    content = new[]
                    {
                        new
                        {
                            is_text = true,
                            text = "Hello World!"
                        }
                    }
                }
            };

            var stubble = new StubbleBuilder().SetMaxRecursionDepth(128).Build();
            var ex =
                Assert.Throws<StubbleException>(() => stubble.Render(rowTemplate, treeData, new Dictionary<string, string>
                {
                    {"row", rowTemplate},
                    {"column", columnTemplate},
                    {"text", textTemplate}
                }));

            Assert.Equal("You have reached the maximum recursion limit of 128.", ex.Message);
        }