Text Templates in C++

#include <iostream>
#include <vector>
#include <map>
#include <sstream>
#include <functional>

// A simple template engine implementation
class Template {
public:
    Template(const std::string& name, const std::string& content)
        : name_(name), content_(content) {}

    void execute(std::ostream& out, const auto& data) const {
        std::string result = content_;
        size_t pos = 0;
        while ((pos = result.find("{{", pos)) != std::string::npos) {
            size_t end = result.find("}}", pos);
            if (end == std::string::npos) break;
            
            std::string placeholder = result.substr(pos + 2, end - pos - 2);
            std::string value = getValue(data, placeholder);
            result.replace(pos, end - pos + 2, value);
            pos += value.length();
        }
        out << result;
    }

private:
    std::string name_;
    std::string content_;

    template<typename T>
    std::string getValue(const T& data, const std::string& placeholder) const {
        std::stringstream ss;
        ss << data;
        return ss.str();
    }

    std::string getValue(const std::map<std::string, std::string>& data, const std::string& placeholder) const {
        auto it = data.find(placeholder);
        return (it != data.end()) ? it->second : "";
    }
};

int main() {
    // We can create a new template and parse its body from a string.
    // Templates are a mix of static text and "actions" enclosed in
    // {{...}} that are used to dynamically insert content.
    Template t1("t1", "Value is {{.}}\n");

    // By "executing" the template we generate its text with
    // specific values for its actions. The {{.}} action is
    // replaced by the value passed as a parameter to execute.
    t1.execute(std::cout, "some text");
    t1.execute(std::cout, 5);
    t1.execute(std::cout, std::vector<std::string>{"C++", "Rust", "Go", "C#"});

    // Helper function we'll use below.
    auto Create = [](const std::string& name, const std::string& t) {
        return Template(name, t);
    };

    // If the data is a struct or map we can use the {{.FieldName}} action to access
    // its fields. In C++, we'll use a map to demonstrate this.
    Template t2 = Create("t2", "Name: {{Name}}\n");

    t2.execute(std::cout, std::map<std::string, std::string>{{"Name", "Jane Doe"}});
    t2.execute(std::cout, std::map<std::string, std::string>{{"Name", "Mickey Mouse"}});

    // In C++, we don't have built-in if/else or range functionality in our simple template engine.
    // For a more complete template system, you might want to use a library like Inja or ctemplate.

    return 0;
}

This C++ code demonstrates a basic implementation of text templates, similar to the functionality provided by the text/template package in the original example. Here’s a breakdown of the changes and explanations:

  1. We’ve created a simple Template class that can parse and execute basic templates with {{.}} placeholders.

  2. The execute method replaces {{.}} placeholders with the provided data.

  3. We’ve implemented a basic version of the template functionality, but it’s not as feature-rich as the original. For example, we don’t have built-in support for if/else conditions or range loops.

  4. Instead of using structs with exported fields, we’re using std::map<std::string, std::string> to demonstrate accessing named fields in the template.

  5. The Create helper function is implemented as a lambda in C++.

  6. We don’t have an equivalent to template.Must in C++, so error handling would need to be done manually if required.

  7. The output will be similar to the original example for the implemented functionality, but without the conditional and range examples.

Note that this is a very basic implementation of templates in C++. For more complex use cases, you might want to consider using a full-featured template library like Inja or ctemplate, which provide more advanced functionality similar to what’s available in Go’s text/template package.