diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h deleted file mode 100644 index 41173b96d1a9a..0000000000000 --- a/llvm/include/llvm/Support/Mustache.h +++ /dev/null @@ -1,127 +0,0 @@ -//===--- Mustache.h ---------------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Implementation of the Mustache templating language supports version 1.4.2 -// currently relies on llvm::json::Value for data input. -// See the Mustache spec for more information -// (https://mustache.github.io/mustache.5.html). -// -// Current Features Supported: -// - Variables -// - Sections -// - Inverted Sections -// - Partials -// - Comments -// - Lambdas -// - Unescaped Variables -// -// Features Not Supported: -// - Set Delimiter -// - Blocks -// - Parents -// - Dynamic Names -// -// The Template class is a container class that outputs the Mustache template -// string and is the main class for users. It stores all the lambdas and the -// ASTNode Tree. When the Template is instantiated it tokenizes the Template -// String and creates a vector of Tokens. Then it calls a basic recursive -// descent parser to construct the ASTNode Tree. The ASTNodes are all stored -// in an arena allocator which is freed once the template class goes out of -// scope. -// -// Usage: -// \code -// // Creating a simple template and rendering it -// auto Template = Template("Hello, {{name}}!"); -// Value Data = {{"name", "World"}}; -// std::string Out; -// raw_string_ostream OS(Out); -// T.render(Data, OS); -// // Out == "Hello, World!" -// -// // Creating a template with a partial and rendering it -// auto Template = Template("{{>partial}}"); -// Template.registerPartial("partial", "Hello, {{name}}!"); -// Value Data = {{"name", "World"}}; -// std::string Out; -// raw_string_ostream OS(Out); -// T.render(Data, OS); -// // Out == "Hello, World!" -// -// // Creating a template with a lambda and rendering it -// Value D = Object{}; -// auto T = Template("Hello, {{lambda}}!"); -// Lambda L = []() -> llvm::json::Value { return "World"; }; -// T.registerLambda("lambda", L); -// std::string Out; -// raw_string_ostream OS(Out); -// T.render(D, OS); -// // Out == "Hello, World!" -// \endcode -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_SUPPORT_MUSTACHE -#define LLVM_SUPPORT_MUSTACHE - -#include "Error.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/Allocator.h" -#include "llvm/Support/JSON.h" -#include "llvm/Support/StringSaver.h" -#include -#include - -namespace llvm::mustache { - -using Lambda = std::function; -using SectionLambda = std::function; - -class ASTNode; - -// A Template represents the container for the AST and the partials -// and Lambdas that are registered with it. -class Template { -public: - Template(StringRef TemplateStr); - - Template(const Template &) = delete; - - Template &operator=(const Template &) = delete; - - Template(Template &&Other) noexcept; - - Template &operator=(Template &&Other) noexcept; - - void render(const llvm::json::Value &Data, llvm::raw_ostream &OS); - - void registerPartial(std::string Name, std::string Partial); - - void registerLambda(std::string Name, Lambda Lambda); - - void registerLambda(std::string Name, SectionLambda Lambda); - - // By default the Mustache Spec Specifies that HTML special characters - // should be escaped. This function allows the user to specify which - // characters should be escaped. - void overrideEscapeCharacters(DenseMap Escapes); - -private: - StringMap Partials; - StringMap Lambdas; - StringMap SectionLambdas; - DenseMap Escapes; - // The allocator for the ASTNode Tree - llvm::BumpPtrAllocator AstAllocator; - // Allocator for each render call resets after each render - llvm::BumpPtrAllocator RenderAllocator; - ASTNode *Tree; -}; -} // namespace llvm::mustache - -#endif // LLVM_SUPPORT_MUSTACHE diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 2754c97fce6c1..49a26a618de83 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -220,7 +220,6 @@ add_llvm_component_library(LLVMSupport MD5.cpp MSP430Attributes.cpp MSP430AttributeParser.cpp - Mustache.cpp NativeFormatting.cpp OptimizedStructLayout.cpp Optional.cpp diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp deleted file mode 100644 index d177432ce1f61..0000000000000 --- a/llvm/lib/Support/Mustache.cpp +++ /dev/null @@ -1,791 +0,0 @@ -//===-- Mustache.cpp ------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#include "llvm/Support/Mustache.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/raw_ostream.h" -#include - -using namespace llvm; -using namespace llvm::mustache; - -namespace { - -using Accessor = SmallVector; - -static bool isFalsey(const json::Value &V) { - return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) || - (V.getAsArray() && V.getAsArray()->empty()); -} - -static Accessor splitMustacheString(StringRef Str) { - // We split the mustache string into an accessor. - // For example: - // "a.b.c" would be split into {"a", "b", "c"} - // We make an exception for a single dot which - // refers to the current context. - Accessor Tokens; - if (Str == ".") { - Tokens.emplace_back(Str); - return Tokens; - } - while (!Str.empty()) { - StringRef Part; - std::tie(Part, Str) = Str.split("."); - Tokens.emplace_back(Part.trim()); - } - return Tokens; -} -} // namespace - -namespace llvm::mustache { - -class Token { -public: - enum class Type { - Text, - Variable, - Partial, - SectionOpen, - SectionClose, - InvertSectionOpen, - UnescapeVariable, - Comment, - }; - - Token(std::string Str) - : TokenType(Type::Text), RawBody(std::move(Str)), TokenBody(RawBody), - Accessor({}), Indentation(0) {}; - - Token(std::string RawBody, std::string TokenBody, char Identifier) - : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)), - Indentation(0) { - TokenType = getTokenType(Identifier); - if (TokenType == Type::Comment) - return; - StringRef AccessorStr(this->TokenBody); - if (TokenType != Type::Variable) - AccessorStr = AccessorStr.substr(1); - Accessor = splitMustacheString(StringRef(AccessorStr).trim()); - } - - Accessor getAccessor() const { return Accessor; } - - Type getType() const { return TokenType; } - - void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; } - - size_t getIndentation() const { return Indentation; } - - static Type getTokenType(char Identifier) { - switch (Identifier) { - case '#': - return Type::SectionOpen; - case '/': - return Type::SectionClose; - case '^': - return Type::InvertSectionOpen; - case '!': - return Type::Comment; - case '>': - return Type::Partial; - case '&': - return Type::UnescapeVariable; - default: - return Type::Variable; - } - } - - Type TokenType; - // RawBody is the original string that was tokenized. - std::string RawBody; - // TokenBody is the original string with the identifier removed. - std::string TokenBody; - Accessor Accessor; - size_t Indentation; -}; - -class ASTNode { -public: - enum Type { - Root, - Text, - Partial, - Variable, - UnescapeVariable, - Section, - InvertSection, - }; - - ASTNode(llvm::BumpPtrAllocator &Alloc, llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) - : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas), - SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Type::Root), - Parent(nullptr), ParentContext(nullptr) {} - - ASTNode(std::string Body, ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) - : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas), - SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Type::Text), - Body(std::move(Body)), Parent(Parent), ParentContext(nullptr) {} - - // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes - ASTNode(Type Ty, Accessor Accessor, ASTNode *Parent, - llvm::BumpPtrAllocator &Alloc, llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) - : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas), - SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Ty), - Parent(Parent), Accessor(std::move(Accessor)), ParentContext(nullptr) {} - - void addChild(ASTNode *Child) { Children.emplace_back(Child); }; - - void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); }; - - void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }; - - void render(const llvm::json::Value &Data, llvm::raw_ostream &OS); - -private: - void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, - Lambda &L); - - void renderSectionLambdas(const llvm::json::Value &Contexts, - llvm::raw_ostream &OS, SectionLambda &L); - - void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, - ASTNode *Partial); - - void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS); - - const llvm::json::Value *findContext(); - - llvm::BumpPtrAllocator &Allocator; - StringMap &Partials; - StringMap &Lambdas; - StringMap &SectionLambdas; - DenseMap &Escapes; - Type Ty; - size_t Indentation = 0; - std::string RawBody; - std::string Body; - ASTNode *Parent; - // TODO: switch implementation to SmallVector - std::vector Children; - const Accessor Accessor; - const llvm::json::Value *ParentContext; -}; - -// A wrapper for arena allocator for ASTNodes -ASTNode *createRootNode(void *Node, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - return new (Node) ASTNode(Alloc, Partials, Lambdas, SectionLambdas, Escapes); -} - -ASTNode *createNode(void *Node, ASTNode::Type T, Accessor A, ASTNode *Parent, - llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - return new (Node) ASTNode(T, std::move(A), Parent, Alloc, Partials, Lambdas, - SectionLambdas, Escapes); -} - -ASTNode *createTextNode(void *Node, std::string Body, ASTNode *Parent, - llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - return new (Node) ASTNode(std::move(Body), Parent, Alloc, Partials, Lambdas, - SectionLambdas, Escapes); -} - -// Function to check if there is meaningful text behind. -// We determine if a token has meaningful text behind -// if the right of previous token contains anything that is -// not a newline. -// For example: -// "Stuff {{#Section}}" (returns true) -// vs -// "{{#Section}} \n" (returns false) -// We make an exception for when previous token is empty -// and the current token is the second token. -// For example: -// "{{#Section}}" -bool hasTextBehind(size_t Idx, const ArrayRef &Tokens) { - if (Idx == 0) - return true; - - size_t PrevIdx = Idx - 1; - if (Tokens[PrevIdx].getType() != Token::Type::Text) - return true; - - const Token &PrevToken = Tokens[PrevIdx]; - StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v"); - return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1); -} - -// Function to check if there's no meaningful text ahead. -// We determine if a token has text ahead if the left of previous -// token does not start with a newline. -bool hasTextAhead(size_t Idx, const ArrayRef &Tokens) { - if (Idx >= Tokens.size() - 1) - return true; - - size_t NextIdx = Idx + 1; - if (Tokens[NextIdx].getType() != Token::Type::Text) - return true; - - const Token &NextToken = Tokens[NextIdx]; - StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" "); - return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n"); -} - -bool requiresCleanUp(Token::Type T) { - // We must clean up all the tokens that could contain child nodes. - return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen || - T == Token::Type::SectionClose || T == Token::Type::Comment || - T == Token::Type::Partial; -} - -// Adjust next token body if there is no text ahead. -// For example: -// The template string -// "{{! Comment }} \nLine 2" -// would be considered as no text ahead and should be rendered as -// " Line 2" -void stripTokenAhead(SmallVectorImpl &Tokens, size_t Idx) { - Token &NextToken = Tokens[Idx + 1]; - StringRef NextTokenBody = NextToken.TokenBody; - // Cut off the leading newline which could be \n or \r\n. - if (NextTokenBody.starts_with("\r\n")) - NextToken.TokenBody = std::move(NextTokenBody.substr(2).str()); - else if (NextTokenBody.starts_with("\n")) - NextToken.TokenBody = std::move(NextTokenBody.substr(1).str()); -} - -// Adjust previous token body if there no text behind. -// For example: -// The template string -// " \t{{#section}}A{{/section}}" -// would be considered as having no text ahead and would be render as -// "A" -// The exception for this is partial tag which requires us to -// keep track of the indentation once it's rendered. -void stripTokenBefore(SmallVectorImpl &Tokens, size_t Idx, - Token &CurrentToken, Token::Type CurrentType) { - Token &PrevToken = Tokens[Idx - 1]; - StringRef PrevTokenBody = PrevToken.TokenBody; - StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v"); - size_t Indentation = PrevTokenBody.size() - Unindented.size(); - if (CurrentType != Token::Type::Partial) - PrevToken.TokenBody = std::move(Unindented.str()); - CurrentToken.setIndentation(Indentation); -} - -// Simple tokenizer that splits the template into tokens. -// The mustache spec allows {{{ }}} to unescape variables, -// but we don't support that here. An unescape variable -// is represented only by {{& variable}}. -SmallVector tokenize(StringRef Template) { - SmallVector Tokens; - StringLiteral Open("{{"); - StringLiteral Close("}}"); - size_t Start = 0; - size_t DelimiterStart = Template.find(Open); - if (DelimiterStart == StringRef::npos) { - Tokens.emplace_back(Template.str()); - return Tokens; - } - while (DelimiterStart != StringRef::npos) { - if (DelimiterStart != Start) - Tokens.emplace_back(Template.substr(Start, DelimiterStart - Start).str()); - size_t DelimiterEnd = Template.find(Close, DelimiterStart); - if (DelimiterEnd == StringRef::npos) - break; - - // Extract the Interpolated variable without delimiters. - size_t InterpolatedStart = DelimiterStart + Open.size(); - size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size(); - std::string Interpolated = - Template.substr(InterpolatedStart, InterpolatedEnd).str(); - std::string RawBody = Open.str() + Interpolated + Close.str(); - Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]); - Start = DelimiterEnd + Close.size(); - DelimiterStart = Template.find(Open, Start); - } - - if (Start < Template.size()) - Tokens.emplace_back(Template.substr(Start).str()); - - // Fix up white spaces for: - // - open sections - // - inverted sections - // - close sections - // - comments - // - // This loop attempts to find standalone tokens and tries to trim out - // the surrounding whitespace. - // For example: - // if you have the template string - // {{#section}} \n Example \n{{/section}} - // The output should would be - // For example: - // \n Example \n - size_t LastIdx = Tokens.size() - 1; - for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) { - Token &CurrentToken = Tokens[Idx]; - Token::Type CurrentType = CurrentToken.getType(); - // Check if token type requires cleanup. - bool RequiresCleanUp = requiresCleanUp(CurrentType); - - if (!RequiresCleanUp) - continue; - - // We adjust the token body if there's no text behind or ahead. - // A token is considered to have no text ahead if the right of the previous - // token is a newline followed by spaces. - // A token is considered to have no text behind if the left of the next - // token is spaces followed by a newline. - // eg. - // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3" - bool HasTextBehind = hasTextBehind(Idx, Tokens); - bool HasTextAhead = hasTextAhead(Idx, Tokens); - - if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0)) - stripTokenAhead(Tokens, Idx); - - if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx)) - stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType); - } - return Tokens; -} - -// Custom stream to escape strings. -class EscapeStringStream : public raw_ostream { -public: - explicit EscapeStringStream(llvm::raw_ostream &WrappedStream, - DenseMap &Escape) - : Escape(Escape), WrappedStream(WrappedStream) { - SetUnbuffered(); - } - -protected: - void write_impl(const char *Ptr, size_t Size) override { - llvm::StringRef Data(Ptr, Size); - for (char C : Data) { - auto It = Escape.find(C); - if (It != Escape.end()) - WrappedStream << It->getSecond(); - else - WrappedStream << C; - } - } - - uint64_t current_pos() const override { return WrappedStream.tell(); } - -private: - DenseMap &Escape; - llvm::raw_ostream &WrappedStream; -}; - -// Custom stream to add indentation used to for rendering partials. -class AddIndentationStringStream : public raw_ostream { -public: - explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream, - size_t Indentation) - : Indentation(Indentation), WrappedStream(WrappedStream) { - SetUnbuffered(); - } - -protected: - void write_impl(const char *Ptr, size_t Size) override { - llvm::StringRef Data(Ptr, Size); - SmallString<0> Indent; - Indent.resize(Indentation, ' '); - for (char C : Data) { - WrappedStream << C; - if (C == '\n') - WrappedStream << Indent; - } - } - - uint64_t current_pos() const override { return WrappedStream.tell(); } - -private: - size_t Indentation; - llvm::raw_ostream &WrappedStream; -}; - -class Parser { -public: - Parser(StringRef TemplateStr, BumpPtrAllocator &Allocator) - : ASTAllocator(Allocator), TemplateStr(TemplateStr) {} - - ASTNode *parse(llvm::BumpPtrAllocator &RenderAlloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes); - -private: - void parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes); - - BumpPtrAllocator &ASTAllocator; - SmallVector Tokens; - size_t CurrentPtr; - StringRef TemplateStr; -}; - -ASTNode *Parser::parse(llvm::BumpPtrAllocator &RenderAlloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - Tokens = tokenize(TemplateStr); - CurrentPtr = 0; - void *Root = ASTAllocator.Allocate(sizeof(ASTNode), alignof(ASTNode)); - ASTNode *RootNode = createRootNode(Root, RenderAlloc, Partials, Lambdas, - SectionLambdas, Escapes); - parseMustache(RootNode, RenderAlloc, Partials, Lambdas, SectionLambdas, - Escapes); - return RootNode; -} - -void Parser::parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc, - llvm::StringMap &Partials, - llvm::StringMap &Lambdas, - llvm::StringMap &SectionLambdas, - llvm::DenseMap &Escapes) { - - while (CurrentPtr < Tokens.size()) { - Token CurrentToken = Tokens[CurrentPtr]; - CurrentPtr++; - Accessor A = CurrentToken.getAccessor(); - ASTNode *CurrentNode; - void *Node = ASTAllocator.Allocate(sizeof(ASTNode), alignof(ASTNode)); - - switch (CurrentToken.getType()) { - case Token::Type::Text: { - CurrentNode = - createTextNode(Node, std::move(CurrentToken.TokenBody), Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - Parent->addChild(CurrentNode); - break; - } - case Token::Type::Variable: { - CurrentNode = - createNode(Node, ASTNode::Variable, std::move(A), Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - Parent->addChild(CurrentNode); - break; - } - case Token::Type::UnescapeVariable: { - CurrentNode = - createNode(Node, ASTNode::UnescapeVariable, std::move(A), Parent, - Alloc, Partials, Lambdas, SectionLambdas, Escapes); - Parent->addChild(CurrentNode); - break; - } - case Token::Type::Partial: { - CurrentNode = - createNode(Node, ASTNode::Partial, std::move(A), Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - CurrentNode->setIndentation(CurrentToken.getIndentation()); - Parent->addChild(CurrentNode); - break; - } - case Token::Type::SectionOpen: { - CurrentNode = createNode(Node, ASTNode::Section, A, Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - size_t Start = CurrentPtr; - parseMustache(CurrentNode, Alloc, Partials, Lambdas, SectionLambdas, - Escapes); - const size_t End = CurrentPtr - 1; - std::string RawBody; - for (std::size_t I = Start; I < End; I++) - RawBody += Tokens[I].RawBody; - CurrentNode->setRawBody(std::move(RawBody)); - Parent->addChild(CurrentNode); - break; - } - case Token::Type::InvertSectionOpen: { - CurrentNode = createNode(Node, ASTNode::InvertSection, A, Parent, Alloc, - Partials, Lambdas, SectionLambdas, Escapes); - size_t Start = CurrentPtr; - parseMustache(CurrentNode, Alloc, Partials, Lambdas, SectionLambdas, - Escapes); - const size_t End = CurrentPtr - 1; - std::string RawBody; - for (size_t Idx = Start; Idx < End; Idx++) - RawBody += Tokens[Idx].RawBody; - CurrentNode->setRawBody(std::move(RawBody)); - Parent->addChild(CurrentNode); - break; - } - case Token::Type::Comment: - break; - case Token::Type::SectionClose: - return; - } - } -} -void toMustacheString(const json::Value &Data, raw_ostream &OS) { - switch (Data.kind()) { - case json::Value::Null: - return; - case json::Value::Number: { - auto Num = *Data.getAsNumber(); - std::ostringstream SS; - SS << Num; - OS << SS.str(); - return; - } - case json::Value::String: { - auto Str = *Data.getAsString(); - OS << Str.str(); - return; - } - - case json::Value::Array: { - auto Arr = *Data.getAsArray(); - if (Arr.empty()) - return; - [[fallthrough]]; - } - case json::Value::Object: - case json::Value::Boolean: { - llvm::json::OStream JOS(OS, 2); - JOS.value(Data); - break; - } - } -} - -void ASTNode::render(const json::Value &Data, raw_ostream &OS) { - ParentContext = &Data; - const json::Value *ContextPtr = Ty == Root ? ParentContext : findContext(); - const json::Value &Context = ContextPtr ? *ContextPtr : nullptr; - - switch (Ty) { - case Root: - renderChild(Data, OS); - return; - case Text: - OS << Body; - return; - case Partial: { - auto Partial = Partials.find(Accessor[0]); - if (Partial != Partials.end()) - renderPartial(Data, OS, Partial->getValue()); - return; - } - case Variable: { - auto Lambda = Lambdas.find(Accessor[0]); - if (Lambda != Lambdas.end()) - renderLambdas(Data, OS, Lambda->getValue()); - else { - EscapeStringStream ES(OS, Escapes); - toMustacheString(Context, ES); - } - return; - } - case UnescapeVariable: { - auto Lambda = Lambdas.find(Accessor[0]); - if (Lambda != Lambdas.end()) - renderLambdas(Data, OS, Lambda->getValue()); - else - toMustacheString(Context, OS); - return; - } - case Section: { - // Sections are not rendered if the context is falsey. - auto SectionLambda = SectionLambdas.find(Accessor[0]); - bool IsLambda = SectionLambda != SectionLambdas.end(); - if (isFalsey(Context) && !IsLambda) - return; - - if (IsLambda) { - renderSectionLambdas(Data, OS, SectionLambda->getValue()); - return; - } - - if (Context.getAsArray()) { - const json::Array *Arr = Context.getAsArray(); - for (const json::Value &V : *Arr) - renderChild(V, OS); - return; - } - renderChild(Context, OS); - return; - } - case InvertSection: { - bool IsLambda = SectionLambdas.find(Accessor[0]) != SectionLambdas.end(); - if (!isFalsey(Context) || IsLambda) - return; - renderChild(Context, OS); - return; - } - } - llvm_unreachable("Invalid ASTNode type"); -} - -const json::Value *ASTNode::findContext() { - // The mustache spec allows for dot notation to access nested values - // a single dot refers to the current context. - // We attempt to find the JSON context in the current node, if it is not - // found, then we traverse the parent nodes to find the context until we - // reach the root node or the context is found. - if (Accessor.empty()) - return nullptr; - if (Accessor[0] == ".") - return ParentContext; - - const json::Object *CurrentContext = ParentContext->getAsObject(); - StringRef CurrentAccessor = Accessor[0]; - ASTNode *CurrentParent = Parent; - - while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) { - if (CurrentParent->Ty != Root) { - CurrentContext = CurrentParent->ParentContext->getAsObject(); - CurrentParent = CurrentParent->Parent; - continue; - } - return nullptr; - } - const json::Value *Context = nullptr; - for (auto [Idx, Acc] : enumerate(Accessor)) { - const json::Value *CurrentValue = CurrentContext->get(Acc); - if (!CurrentValue) - return nullptr; - if (Idx < Accessor.size() - 1) { - CurrentContext = CurrentValue->getAsObject(); - if (!CurrentContext) - return nullptr; - } else - Context = CurrentValue; - } - return Context; -} - -void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) { - for (ASTNode *Child : Children) - Child->render(Contexts, OS); -} - -void ASTNode::renderPartial(const json::Value &Contexts, llvm::raw_ostream &OS, - ASTNode *Partial) { - AddIndentationStringStream IS(OS, Indentation); - Partial->render(Contexts, IS); -} - -void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS, - Lambda &L) { - json::Value LambdaResult = L(); - std::string LambdaStr; - raw_string_ostream Output(LambdaStr); - toMustacheString(LambdaResult, Output); - Parser P = Parser(LambdaStr, Allocator); - ASTNode *LambdaNode = - P.parse(Allocator, Partials, Lambdas, SectionLambdas, Escapes); - - EscapeStringStream ES(OS, Escapes); - if (Ty == Variable) { - LambdaNode->render(Contexts, ES); - return; - } - LambdaNode->render(Contexts, OS); -} - -void ASTNode::renderSectionLambdas(const json::Value &Contexts, - llvm::raw_ostream &OS, SectionLambda &L) { - json::Value Return = L(RawBody); - if (isFalsey(Return)) - return; - std::string LambdaStr; - raw_string_ostream Output(LambdaStr); - toMustacheString(Return, Output); - Parser P = Parser(LambdaStr, Allocator); - ASTNode *LambdaNode = - P.parse(Allocator, Partials, Lambdas, SectionLambdas, Escapes); - LambdaNode->render(Contexts, OS); - return; -} - -void Template::render(const json::Value &Data, llvm::raw_ostream &OS) { - Tree->render(Data, OS); - RenderAllocator.Reset(); -} - -void Template::registerPartial(std::string Name, std::string Partial) { - Parser P = Parser(Partial, AstAllocator); - ASTNode *PartialTree = - P.parse(RenderAllocator, Partials, Lambdas, SectionLambdas, Escapes); - Partials.insert(std::make_pair(Name, PartialTree)); -} - -void Template::registerLambda(std::string Name, Lambda L) { Lambdas[Name] = L; } - -void Template::registerLambda(std::string Name, SectionLambda L) { - SectionLambdas[Name] = L; -} - -void Template::overrideEscapeCharacters(DenseMap E) { - Escapes = std::move(E); -} - -Template::Template(StringRef TemplateStr) { - Parser P = Parser(TemplateStr, AstAllocator); - Tree = P.parse(RenderAllocator, Partials, Lambdas, SectionLambdas, Escapes); - // The default behavior is to escape html entities. - DenseMap HtmlEntities = {{'&', "&"}, - {'<', "<"}, - {'>', ">"}, - {'"', """}, - {'\'', "'"}}; - overrideEscapeCharacters(HtmlEntities); -} - -Template::Template(Template &&Other) noexcept - : Partials(std::move(Other.Partials)), Lambdas(std::move(Other.Lambdas)), - SectionLambdas(std::move(Other.SectionLambdas)), - Escapes(std::move(Other.Escapes)), Tree(Other.Tree), - AstAllocator(std::move(Other.AstAllocator)), - RenderAllocator(std::move(Other.RenderAllocator)) { - Other.Tree = nullptr; -} - -Template &Template::operator=(Template &&Other) noexcept { - if (this != &Other) { - Partials = std::move(Other.Partials); - Lambdas = std::move(Other.Lambdas); - SectionLambdas = std::move(Other.SectionLambdas); - Escapes = std::move(Other.Escapes); - Tree = Other.Tree; - AstAllocator = std::move(Other.AstAllocator); - RenderAllocator = std::move(Other.RenderAllocator); - Other.Tree = nullptr; - } - return *this; -} -} // namespace llvm::mustache diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index 6c4e7cb689b20..6de8165826442 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -61,7 +61,6 @@ add_llvm_unittest(SupportTests MemoryBufferRefTest.cpp MemoryBufferTest.cpp MemoryTest.cpp - MustacheTest.cpp ModRefTest.cpp NativeFormatTests.cpp OptimizedStructLayoutTest.cpp diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp deleted file mode 100644 index 6ab3d4b01bc1b..0000000000000 --- a/llvm/unittests/Support/MustacheTest.cpp +++ /dev/null @@ -1,1226 +0,0 @@ -//===- llvm/unittest/Support/MustacheTest.cpp ----------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Test conforming to Mustache 1.4.2 spec found here: -// https://github.com/mustache/spec -// -//===----------------------------------------------------------------------===// - -#include "llvm/Support/Mustache.h" -#include "llvm/Support/raw_ostream.h" -#include "gtest/gtest.h" - -using namespace llvm; -using namespace llvm::mustache; -using namespace llvm::json; - -TEST(MustacheInterpolation, NoInterpolation) { - // Mustache-free templates should render as-is. - Value D = {}; - auto T = Template("Hello from {Mustache}!\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello from {Mustache}!\n", Out); -} - -TEST(MustacheInterpolation, BasicInterpolation) { - // Unadorned tags should interpolate content into the template. - Value D = Object{{"subject", "World"}}; - auto T = Template("Hello, {{subject}}!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, World!", Out); -} - -TEST(MustacheInterpolation, NoReinterpolation) { - // Interpolated tag output should not be re-interpolated. - Value D = Object{{"template", "{{planet}}"}, {"planet", "Earth"}}; - auto T = Template("{{template}}: {{planet}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("{{planet}}: Earth", Out); -} - -TEST(MustacheInterpolation, HTMLEscaping) { - // Interpolated tag output should not be re-interpolated. - Value D = Object{ - {"forbidden", "& \" < >"}, - }; - auto T = Template("These characters should be HTML escaped: {{forbidden}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("These characters should be HTML escaped: & " < >\n", - Out); -} - -TEST(MustacheInterpolation, Ampersand) { - // Interpolated tag output should not be re-interpolated. - Value D = Object{ - {"forbidden", "& \" < >"}, - }; - auto T = - Template("These characters should not be HTML escaped: {{&forbidden}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out); -} - -TEST(MustacheInterpolation, BasicIntegerInterpolation) { - // Integers should interpolate seamlessly. - Value D = Object{{"mph", 85}}; - auto T = Template("{{mph}} miles an hour!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("85 miles an hour!", Out); -} - -TEST(MustacheInterpolation, AmpersandIntegerInterpolation) { - // Integers should interpolate seamlessly. - Value D = Object{{"mph", 85}}; - auto T = Template("{{&mph}} miles an hour!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("85 miles an hour!", Out); -} - -TEST(MustacheInterpolation, BasicDecimalInterpolation) { - // Decimals should interpolate seamlessly with proper significance. - Value D = Object{{"power", 1.21}}; - auto T = Template("{{power}} jiggawatts!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("1.21 jiggawatts!", Out); -} - -TEST(MustacheInterpolation, BasicNullInterpolation) { - // Nulls should interpolate as the empty string. - Value D = Object{{"cannot", nullptr}}; - auto T = Template("I ({{cannot}}) be seen!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("I () be seen!", Out); -} - -TEST(MustacheInterpolation, AmpersandNullInterpolation) { - // Nulls should interpolate as the empty string. - Value D = Object{{"cannot", nullptr}}; - auto T = Template("I ({{&cannot}}) be seen!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("I () be seen!", Out); -} - -TEST(MustacheInterpolation, BasicContextMissInterpolation) { - // Failed context lookups should default to empty strings. - Value D = Object{}; - auto T = Template("I ({{cannot}}) be seen!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("I () be seen!", Out); -} - -TEST(MustacheInterpolation, DottedNamesBasicInterpolation) { - // Dotted names should be considered a form of shorthand for sections. - Value D = Object{{"person", Object{{"name", "Joe"}}}}; - auto T = Template("{{person.name}} == {{#person}}{{name}}{{/person}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Joe == Joe", Out); -} - -TEST(MustacheInterpolation, DottedNamesAmpersandInterpolation) { - // Dotted names should be considered a form of shorthand for sections. - Value D = Object{{"person", Object{{"name", "Joe"}}}}; - auto T = Template("{{&person.name}} == {{#person}}{{&name}}{{/person}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Joe == Joe", Out); -} - -TEST(MustacheInterpolation, DottedNamesArbitraryDepth) { - // Dotted names should be functional to any level of nesting. - Value D = Object{ - {"a", - Object{{"b", - Object{{"c", - Object{{"d", - Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}}; - auto T = Template("{{a.b.c.d.e.name}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Phil", Out); -} - -TEST(MustacheInterpolation, DottedNamesBrokenChains) { - // Any falsey value prior to the last part of the name should yield ''. - Value D = Object{{"a", Object{}}}; - auto T = Template("{{a.b.c}} == "); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" == ", Out); -} - -TEST(MustacheInterpolation, DottedNamesBrokenChainResolution) { - // Each part of a dotted name should resolve only against its parent. - Value D = - Object{{"a", Object{{"b", Object{}}}}, {"c", Object{{"name", "Jim"}}}}; - auto T = Template("{{a.b.c.name}} == "); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" == ", Out); -} - -TEST(MustacheInterpolation, DottedNamesInitialResolution) { - // The first part of a dotted name should resolve as any other name. - Value D = Object{ - {"a", - Object{ - {"b", - Object{{"c", - Object{{"d", Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}, - {"b", - Object{{"c", Object{{"d", Object{{"e", Object{{"name", "Wrong"}}}}}}}}}}; - auto T = Template("{{#a}}{{b.c.d.e.name}}{{/a}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Phil", Out); -} - -TEST(MustacheInterpolation, DottedNamesContextPrecedence) { - // Dotted names should be resolved against former resolutions. - Value D = - Object{{"a", Object{{"b", Object{}}}}, {"b", Object{{"c", "ERROR"}}}}; - auto T = Template("{{#a}}{{b.c}}{{/a}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheInterpolation, DottedNamesAreNotSingleKeys) { - // Dotted names shall not be parsed as single, atomic keys - Value D = Object{{"a.b", "c"}}; - auto T = Template("{{a.b}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheInterpolation, DottedNamesNoMasking) { - // Dotted Names in a given context are unavailable due to dot splitting - Value D = Object{{"a.b", "c"}, {"a", Object{{"b", "d"}}}}; - auto T = Template("{{a.b}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("d", Out); -} - -TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) { - // Unadorned tags should interpolate content into the template. - Value D = "world"; - auto T = Template("Hello, {{.}}!\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, world!\n", Out); -} - -TEST(MustacheInterpolation, ImplicitIteratorsAmersand) { - // Basic interpolation should be HTML escaped. - Value D = "& \" < >"; - auto T = Template("These characters should not be HTML escaped: {{&.}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out); -} - -TEST(MustacheInterpolation, ImplicitIteratorsInteger) { - // Integers should interpolate seamlessly. - Value D = 85; - auto T = Template("{{.}} miles an hour!\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("85 miles an hour!\n", Out); -} - -TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) { - // Interpolation should not alter surrounding whitespace. - Value D = Object{{"string", "---"}}; - auto T = Template("| {{string}} |"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| --- |", Out); -} - -TEST(MustacheInterpolation, AmersandSurroundingWhitespace) { - // Interpolation should not alter surrounding whitespace. - Value D = Object{{"string", "---"}}; - auto T = Template("| {{&string}} |"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| --- |", Out); -} - -TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) { - // Standalone interpolation should not alter surrounding whitespace. - Value D = Object{{"string", "---"}}; - auto T = Template(" {{string}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" ---\n", Out); -} - -TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) { - // Standalone interpolation should not alter surrounding whitespace. - Value D = Object{{"string", "---"}}; - auto T = Template(" {{&string}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" ---\n", Out); -} - -TEST(MustacheInterpolation, InterpolationWithPadding) { - // Superfluous in-tag whitespace should be ignored. - Value D = Object{{"string", "---"}}; - auto T = Template("|{{ string }}|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|---|", Out); -} - -TEST(MustacheInterpolation, AmpersandWithPadding) { - // Superfluous in-tag whitespace should be ignored. - Value D = Object{{"string", "---"}}; - auto T = Template("|{{& string }}|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|---|", Out); -} - -TEST(MustacheInterpolation, InterpolationWithPaddingAndNewlines) { - // Superfluous in-tag whitespace should be ignored. - Value D = Object{{"string", "---"}}; - auto T = Template("|{{ string \n\n\n }}|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|---|", Out); -} - -TEST(MustacheSections, Truthy) { - Value D = Object{{"boolean", true}}; - auto T = Template("{{#boolean}}This should be rendered.{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("This should be rendered.", Out); -} - -TEST(MustacheSections, Falsey) { - Value D = Object{{"boolean", false}}; - auto T = Template("{{#boolean}}This should not be rendered.{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheInterpolation, IsFalseyNull) { - // Mustache-free templates should render as-is. - Value D = Object{{"boolean", nullptr}}; - auto T = Template("Hello, {{#boolean}}World{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, ", Out); -} - -TEST(MustacheInterpolation, IsFalseyArray) { - // Mustache-free templates should render as-is. - Value D = Object{{"boolean", Array()}}; - auto T = Template("Hello, {{#boolean}}World{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, ", Out); -} - -TEST(MustacheInterpolation, IsFalseyObject) { - // Mustache-free templates should render as-is. - Value D = Object{{"boolean", Object{}}}; - auto T = Template("Hello, {{#boolean}}World{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, World", Out); -} - -TEST(MustacheInterpolation, DoubleRendering) { - // Mustache-free templates should render as-is. - Value D1 = Object{{"subject", "World"}}; - auto T = Template("Hello, {{subject}}!"); - std::string Out1; - raw_string_ostream OS1(Out1); - T.render(D1, OS1); - EXPECT_EQ("Hello, World!", Out1); - std::string Out2; - raw_string_ostream OS2(Out2); - Value D2 = Object{{"subject", "New World"}}; - T.render(D2, OS2); - EXPECT_EQ("Hello, New World!", Out2); -} - -TEST(MustacheSections, NullIsFalsey) { - Value D = Object{{"null", nullptr}}; - auto T = Template("{{#null}}This should not be rendered.{{/null}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheSections, Context) { - Value D = Object{{"context", Object{{"name", "Joe"}}}}; - auto T = Template("{{#context}}Hi {{name}}.{{/context}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hi Joe.", Out); -} - -TEST(MustacheSections, ParentContexts) { - Value D = Object{{"a", "foo"}, - {"b", "wrong"}, - {"sec", Object{{"b", "bar"}}}, - {"c", Object{{"d", "baz"}}}}; - auto T = Template("{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("foo, bar, baz", Out); -} - -TEST(MustacheSections, VariableTest) { - Value D = Object{{"foo", "bar"}}; - auto T = Template("{{#foo}}{{.}} is {{foo}}{{/foo}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("bar is bar", Out); -} - -TEST(MustacheSections, ListContexts) { - Value D = Object{ - {"tops", - Array{Object{ - {"tname", Object{{"upper", "A"}, {"lower", "a"}}}, - {"middles", - Array{Object{{"mname", "1"}, - {"bottoms", Array{Object{{"bname", "x"}}, - Object{{"bname", "y"}}}}}}}}}}}; - auto T = Template("{{#tops}}" - "{{#middles}}" - "{{tname.lower}}{{mname}}." - "{{#bottoms}}" - "{{tname.upper}}{{mname}}{{bname}}." - "{{/bottoms}}" - "{{/middles}}" - "{{/tops}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("a1.A1x.A1y.", Out); -} - -TEST(MustacheSections, DeeplyNestedContexts) { - Value D = Object{ - {"a", Object{{"one", 1}}}, - {"b", Object{{"two", 2}}}, - {"c", Object{{"three", 3}, {"d", Object{{"four", 4}, {"five", 5}}}}}}; - auto T = Template( - "{{#a}}\n{{one}}\n{{#b}}\n{{one}}{{two}}{{one}}\n{{#c}}\n{{one}}{{two}}{{" - "three}}{{two}}{{one}}\n{{#d}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{" - "{two}}{{one}}\n{{#five}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{four}" - "}{{three}}{{two}}{{one}}\n{{one}}{{two}}{{three}}{{four}}{{.}}6{{.}}{{" - "four}}{{three}}{{two}}{{one}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{" - "four}}{{three}}{{two}}{{one}}\n{{/" - "five}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{/" - "d}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{/" - "c}}\n{{one}}{{two}}{{one}}\n{{/b}}\n{{one}}\n{{/a}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("1\n121\n12321\n1234321\n123454321\n12345654321\n123454321\n1234321" - "\n12321\n121\n1\n", - Out); -} - -TEST(MustacheSections, List) { - Value D = Object{{"list", Array{Object{{"item", 1}}, Object{{"item", 2}}, - Object{{"item", 3}}}}}; - auto T = Template("{{#list}}{{item}}{{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("123", Out); -} - -TEST(MustacheSections, EmptyList) { - Value D = Object{{"list", Array{}}}; - auto T = Template("{{#list}}Yay lists!{{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheSections, Doubled) { - Value D = Object{{"bool", true}, {"two", "second"}}; - auto T = Template("{{#bool}}\n* first\n{{/bool}}\n* " - "{{two}}\n{{#bool}}\n* third\n{{/bool}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("* first\n* second\n* third\n", Out); -} - -TEST(MustacheSections, NestedTruthy) { - Value D = Object{{"bool", true}}; - auto T = Template("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| A B C D E |", Out); -} - -TEST(MustacheSections, NestedFalsey) { - Value D = Object{{"bool", false}}; - auto T = Template("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| A E |", Out); -} - -TEST(MustacheSections, ContextMisses) { - Value D = Object{}; - auto T = Template("[{{#missing}}Found key 'missing'!{{/missing}}]"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("[]", Out); -} - -TEST(MustacheSections, ImplicitIteratorString) { - Value D = Object{{"list", Array{"a", "b", "c", "d", "e"}}}; - auto T = Template("{{#list}}({{.}}){{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("(a)(b)(c)(d)(e)", Out); -} - -TEST(MustacheSections, ImplicitIteratorInteger) { - Value D = Object{{"list", Array{1, 2, 3, 4, 5}}}; - auto T = Template("{{#list}}({{.}}){{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("(1)(2)(3)(4)(5)", Out); -} - -TEST(MustacheSections, ImplicitIteratorArray) { - Value D = Object{{"list", Array{Array{1, 2, 3}, Array{"a", "b", "c"}}}}; - auto T = Template("{{#list}}({{#.}}{{.}}{{/.}}){{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("(123)(abc)", Out); -} - -TEST(MustacheSections, ImplicitIteratorHTMLEscaping) { - Value D = Object{{"list", Array{"&", "\"", "<", ">"}}}; - auto T = Template("{{#list}}({{.}}){{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("(&)(")(<)(>)", Out); -} - -TEST(MustacheSections, ImplicitIteratorAmpersand) { - Value D = Object{{"list", Array{"&", "\"", "<", ">"}}}; - auto T = Template("{{#list}}({{&.}}){{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("(&)(\")(<)(>)", Out); -} - -TEST(MustacheSections, ImplicitIteratorRootLevel) { - Value D = Array{Object{{"value", "a"}}, Object{{"value", "b"}}}; - auto T = Template("{{#.}}({{value}}){{/.}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("(a)(b)", Out); -} - -TEST(MustacheSections, DottedNamesTruthy) { - Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}}; - auto T = Template("{{#a.b.c}}Here{{/a.b.c}} == Here"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Here == Here", Out); -} - -TEST(MustacheSections, DottedNamesFalsey) { - Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}}; - auto T = Template("{{#a.b.c}}Here{{/a.b.c}} == "); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" == ", Out); -} - -TEST(MustacheSections, DottedNamesBrokenChains) { - Value D = Object{{"a", Object{}}}; - auto T = Template("{{#a.b.c}}Here{{/a.b.c}} == "); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" == ", Out); -} - -TEST(MustacheSections, SurroundingWhitespace) { - Value D = Object{{"boolean", true}}; - auto T = Template(" | {{#boolean}}\t|\t{{/boolean}} | \n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" | \t|\t | \n", Out); -} - -TEST(MustacheSections, InternalWhitespace) { - Value D = Object{{"boolean", true}}; - auto T = Template( - " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" | \n | \n", Out); -} - -TEST(MustacheSections, IndentedInlineSections) { - Value D = Object{{"boolean", true}}; - auto T = - Template(" {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" YES\n GOOD\n", Out); -} - -TEST(MustacheSections, StandaloneLines) { - Value D = Object{{"boolean", true}}; - auto T = Template("| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| This Is\n|\n| A Line\n", Out); -} - -TEST(MustacheSections, IndentedStandaloneLines) { - Value D = Object{{"boolean", true}}; - auto T = Template("| This Is\n {{#boolean}}\n|\n {{/boolean}}\n| A Line\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| This Is\n|\n| A Line\n", Out); -} - -TEST(MustacheSections, StandaloneLineEndings) { - Value D = Object{{"boolean", true}}; - auto T = Template("|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|\r\n|", Out); -} - -TEST(MustacheSections, StandaloneWithoutPreviousLine) { - Value D = Object{{"boolean", true}}; - auto T = Template(" {{#boolean}}\n#{{/boolean}}\n/"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("#\n/", Out); -} - -TEST(MustacheSections, StandaloneWithoutNewline) { - Value D = Object{{"boolean", true}}; - auto T = Template("#{{#boolean}}\n/\n {{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("#\n/\n", Out); -} - -TEST(MustacheSections, Padding) { - Value D = Object{{"boolean", true}}; - auto T = Template("|{{# boolean }}={{/ boolean }}|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|=|", Out); -} - -TEST(MustacheInvertedSections, Falsey) { - Value D = Object{{"boolean", false}}; - auto T = Template("{{^boolean}}This should be rendered.{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("This should be rendered.", Out); -} - -TEST(MustacheInvertedSections, Truthy) { - Value D = Object{{"boolean", true}}; - auto T = Template("{{^boolean}}This should not be rendered.{{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheInvertedSections, NullIsFalsey) { - Value D = Object{{"null", nullptr}}; - auto T = Template("{{^null}}This should be rendered.{{/null}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("This should be rendered.", Out); -} - -TEST(MustacheInvertedSections, Context) { - Value D = Object{{"context", Object{{"name", "Joe"}}}}; - auto T = Template("{{^context}}Hi {{name}}.{{/context}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheInvertedSections, List) { - Value D = Object{ - {"list", Array{Object{{"n", 1}}, Object{{"n", 2}}, Object{{"n", 3}}}}}; - auto T = Template("{{^list}}{{n}}{{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheInvertedSections, EmptyList) { - Value D = Object{{"list", Array{}}}; - auto T = Template("{{^list}}Yay lists!{{/list}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Yay lists!", Out); -} - -TEST(MustacheInvertedSections, Doubled) { - Value D = Object{{"bool", false}, {"two", "second"}}; - auto T = Template("{{^bool}}\n* first\n{{/bool}}\n* " - "{{two}}\n{{^bool}}\n* third\n{{/bool}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("* first\n* second\n* third\n", Out); -} - -TEST(MustacheInvertedSections, NestedFalsey) { - Value D = Object{{"bool", false}}; - auto T = Template("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| A B C D E |", Out); -} - -TEST(MustacheInvertedSections, NestedTruthy) { - Value D = Object{{"bool", true}}; - auto T = Template("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| A E |", Out); -} - -TEST(MustacheInvertedSections, ContextMisses) { - Value D = Object{}; - auto T = Template("[{{^missing}}Cannot find key 'missing'!{{/missing}}]"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("[Cannot find key 'missing'!]", Out); -} - -TEST(MustacheInvertedSections, DottedNamesTruthy) { - Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}}; - auto T = Template("{{^a.b.c}}Not Here{{/a.b.c}} == "); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" == ", Out); -} - -TEST(MustacheInvertedSections, DottedNamesFalsey) { - Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}}; - auto T = Template("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Not Here == Not Here", Out); -} - -TEST(MustacheInvertedSections, DottedNamesBrokenChains) { - Value D = Object{{"a", Object{}}}; - auto T = Template("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Not Here == Not Here", Out); -} - -TEST(MustacheInvertedSections, SurroundingWhitespace) { - Value D = Object{{"boolean", false}}; - auto T = Template(" | {{^boolean}}\t|\t{{/boolean}} | \n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" | \t|\t | \n", Out); -} - -TEST(MustacheInvertedSections, InternalWhitespace) { - Value D = Object{{"boolean", false}}; - auto T = Template( - " | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" | \n | \n", Out); -} - -TEST(MustacheInvertedSections, IndentedInlineSections) { - Value D = Object{{"boolean", false}}; - auto T = - Template(" {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" NO\n WAY\n", Out); -} - -TEST(MustacheInvertedSections, StandaloneLines) { - Value D = Object{{"boolean", false}}; - auto T = Template("| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| This Is\n|\n| A Line\n", Out); -} - -TEST(MustacheInvertedSections, StandaloneIndentedLines) { - Value D = Object{{"boolean", false}}; - auto T = Template("| This Is\n {{^boolean}}\n|\n {{/boolean}}\n| A Line\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| This Is\n|\n| A Line\n", Out); -} - -TEST(MustacheInvertedSections, StandaloneLineEndings) { - Value D = Object{{"boolean", false}}; - auto T = Template("|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|\r\n|", Out); -} - -TEST(MustacheInvertedSections, StandaloneWithoutPreviousLine) { - Value D = Object{{"boolean", false}}; - auto T = Template(" {{^boolean}}\n^{{/boolean}}\n/"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("^\n/", Out); -} - -TEST(MustacheInvertedSections, StandaloneWithoutNewline) { - Value D = Object{{"boolean", false}}; - auto T = Template("^{{^boolean}}\n/\n {{/boolean}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("^\n/\n", Out); -} - -TEST(MustacheInvertedSections, Padding) { - Value D = Object{{"boolean", false}}; - auto T = Template("|{{^ boolean }}={{/ boolean }}|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|=|", Out); -} - -TEST(MustachePartials, BasicBehavior) { - Value D = Object{}; - auto T = Template("{{>text}}"); - T.registerPartial("text", "from partial"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("from partial", Out); -} - -TEST(MustachePartials, FailedLookup) { - Value D = Object{}; - auto T = Template("{{>text}}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustachePartials, Context) { - Value D = Object{{"text", "content"}}; - auto T = Template("{{>partial}}"); - T.registerPartial("partial", "*{{text}}*"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("*content*", Out); -} - -TEST(MustachePartials, Recursion) { - Value D = - Object{{"content", "X"}, - {"nodes", Array{Object{{"content", "Y"}, {"nodes", Array{}}}}}}; - auto T = Template("{{>node}}"); - T.registerPartial("node", "{{content}}({{#nodes}}{{>node}}{{/nodes}})"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("X(Y())", Out); -} - -TEST(MustachePartials, Nested) { - Value D = Object{{"a", "hello"}, {"b", "world"}}; - auto T = Template("{{>outer}}"); - T.registerPartial("outer", "*{{a}} {{>inner}}*"); - T.registerPartial("inner", "{{b}}!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("*hello world!*", Out); -} - -TEST(MustachePartials, SurroundingWhitespace) { - Value D = Object{}; - auto T = Template("| {{>partial}} |"); - T.registerPartial("partial", "\t|\t"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("| \t|\t |", Out); -} - -TEST(MustachePartials, InlineIndentation) { - Value D = Object{{"data", "|"}}; - auto T = Template(" {{data}} {{> partial}}\n"); - T.registerPartial("partial", "<\n<"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" | <\n<\n", Out); -} - -TEST(MustachePartials, PaddingWhitespace) { - Value D = Object{{"boolean", true}}; - auto T = Template("|{{> partial }}|"); - T.registerPartial("partial", "[]"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|[]|", Out); -} - -TEST(MustacheLambdas, BasicInterpolation) { - Value D = Object{}; - auto T = Template("Hello, {{lambda}}!"); - Lambda L = []() -> llvm::json::Value { return "World"; }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, World!", Out); -} - -TEST(MustacheLambdas, InterpolationExpansion) { - Value D = Object{{"planet", "World"}}; - auto T = Template("Hello, {{lambda}}!"); - Lambda L = []() -> llvm::json::Value { return "{{planet}}"; }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Hello, World!", Out); -} - -TEST(MustacheLambdas, BasicMultipleCalls) { - Value D = Object{}; - auto T = Template("{{lambda}} == {{lambda}} == {{lambda}}"); - int I = 0; - Lambda L = [&I]() -> llvm::json::Value { - I += 1; - return I; - }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("1 == 2 == 3", Out); -} - -TEST(MustacheLambdas, Escaping) { - Value D = Object{}; - auto T = Template("<{{lambda}}{{&lambda}}"); - Lambda L = []() -> llvm::json::Value { return ">"; }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("<>>", Out); -} - -TEST(MustacheLambdas, Sections) { - Value D = Object{}; - auto T = Template("<{{#lambda}}{{x}}{{/lambda}}>"); - SectionLambda L = [](StringRef Text) -> llvm::json::Value { - if (Text == "{{x}}") { - return "yes"; - } - return "no"; - }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("", Out); -} - -TEST(MustacheLambdas, SectionExpansion) { - Value D = Object{ - {"planet", "Earth"}, - }; - auto T = Template("<{{#lambda}}-{{/lambda}}>"); - SectionLambda L = [](StringRef Text) -> llvm::json::Value { - SmallString<128> Result; - Result += Text; - Result += "{{planet}}"; - Result += Text; - return Result; - }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("<-Earth->", Out); -} - -TEST(MustacheLambdas, SectionsMultipleCalls) { - Value D = Object{}; - auto T = Template("{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}"); - SectionLambda L = [](StringRef Text) -> llvm::json::Value { - SmallString<128> Result; - Result += "__"; - Result += Text; - Result += "__"; - return Result; - }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("__FILE__ != __LINE__", Out); -} - -TEST(MustacheLambdas, InvertedSections) { - Value D = Object{{"static", "static"}}; - auto T = Template("<{{^lambda}}{{static}}{{/lambda}}>"); - SectionLambda L = [](StringRef Text) -> llvm::json::Value { return false; }; - T.registerLambda("lambda", L); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("<>", Out); -} - -TEST(MustacheComments, Inline) { - // Comment blocks should be removed from the template. - Value D = {}; - auto T = Template("12345{{! Comment Block! }}67890"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("1234567890", Out); -} - -TEST(MustacheComments, Multiline) { - // Multiline comments should be permitted. - Value D = {}; - auto T = - Template("12345{{!\n This is a\n multi-line comment...\n}}67890\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("1234567890\n", Out); -} - -TEST(MustacheComments, Standalone) { - // All standalone comment lines should be removed. - Value D = {}; - auto T = Template("Begin.\n{{! Comment Block! }}\nEnd.\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Begin.\nEnd.\n", Out); -} - -TEST(MustacheComments, IndentedStandalone) { - // All standalone comment lines should be removed. - Value D = {}; - auto T = Template("Begin.\n {{! Indented Comment Block! }}\nEnd.\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Begin.\nEnd.\n", Out); -} - -TEST(MustacheComments, StandaloneLineEndings) { - // "\r\n" should be considered a newline for standalone tags. - Value D = {}; - auto T = Template("|\r\n{{! Standalone Comment }}\r\n|"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("|\r\n|", Out); -} - -TEST(MustacheComments, StandaloneWithoutPreviousLine) { - // Standalone tags should not require a newline to precede them. - Value D = {}; - auto T = Template(" {{! I'm Still Standalone }}\n!"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("!", Out); -} - -TEST(MustacheComments, StandaloneWithoutNewline) { - // Standalone tags should not require a newline to follow them. - Value D = {}; - auto T = Template("!\n {{! I'm Still Standalone }}"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("!\n", Out); -} - -TEST(MustacheComments, MultilineStandalone) { - // All standalone comment lines should be removed. - Value D = {}; - auto T = Template("Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Begin.\nEnd.\n", Out); -} - -TEST(MustacheComments, IndentedMultilineStandalone) { - // All standalone comment lines should be removed. - Value D = {}; - auto T = - Template("Begin.\n {{!\n Something's going on here...\n }}\nEnd.\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("Begin.\nEnd.\n", Out); -} - -TEST(MustacheComments, IndentedInline) { - // Inline comments should not strip whitespace. - Value D = {}; - auto T = Template(" 12 {{! 34 }}\n"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ(" 12 \n", Out); -} - -TEST(MustacheComments, SurroundingWhitespace) { - // Comment removal should preserve surrounding whitespace. - Value D = {}; - auto T = Template("12345 {{! Comment Block! }} 67890"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("12345 67890", Out); -} - -TEST(MustacheComments, VariableNameCollision) { - // Comments must never render, even if a variable with the same name exists. - Value D = Object{ - {"! comment", 1}, {"! comment ", 2}, {"!comment", 3}, {"comment", 4}}; - auto T = Template("comments never show: >{{! comment }}<"); - std::string Out; - raw_string_ostream OS(Out); - T.render(D, OS); - EXPECT_EQ("comments never show: ><", Out); -}