jamboree / bustache

C++20 implementation of {{ mustache }}
82 stars 10 forks source link

Trouble getting Partials (embedded templates) working when using the Nlohmann Json adapter. #36

Closed luz-arreola closed 10 months ago

luz-arreola commented 10 months ago

I am using Bustache heavily in a multi-threaded application using the adapter for Nlohmann Json found here: [https://github.com/jamboree/bustache/blob/master/example/nlohmann.cpp]

Bustache works perfectly well with this adapter, except that I cannot get partials to work with it.

Here is a minimalist example:

void test() {

    std::unordered_map<std::string, bustache::format> context
    {
        {"EMBEDDED_TEMPLATE", ", goodbye {{goodbyeTag}}"_fmt}
    };

    nlohmann::json data;
    data["helloTag"] = "Bustache";
    data["goodbyeTag"] = "Mustache";

    bustache::format format("Hello {{helloTag}}{{>EMBEDDED_TEMPLATE}} templating system.\n");

    // Prints: Hello Bustache templating system.
    std::cout << format(data) << "\n";

    // Should print: Hello Bustache, goodbye Mustache templating system.
    auto str = format(data).context(context);
    std::cout << str << '\n';   // 'render_ostream': no matching overloaded function
}

Bustache works so nicely with Nlohmann, and my hope is that the Jamboree or some other knowledgeable person can adjust the code for partials for this adapter. Thanks!

jamboree commented 10 months ago

Are you using some old version? I took your example and it works fine on the current version.

Note that Context Handler is a bit different in new API, see here for example.

luz-arreola commented 10 months ago

I did have a slightly old version and have now installed the latest source. This is how I've been accessing the processed data successfully:

auto str1 = bustache::to_string(format(data));

But this is where I get a compiler error when I try to use partials:

auto str2 = bustache::to_string(format(data).context(context)); // 'render_string': no matching overloaded function found

I looked at your context handler and example link but I'm lost, I just can't figure out the proper syntax to use, please help.

jamboree commented 10 months ago

Here's a self-contained working version of your example:

#include <iostream>
#include <bustache/render/ostream.hpp>
#include <nlohmann/json.hpp>

template<>
struct bustache::impl_model<nlohmann::json> {
    impl_model() = delete;
};

template<>
struct bustache::impl_compatible<nlohmann::json> {
    static value_ptr get_value_ptr(nlohmann::json const& self) {
        nlohmann::json::value_t const kind(self);
        switch (kind) {
        case nlohmann::json::value_t::boolean:
            return value_ptr(self.get_ptr<nlohmann::json::boolean_t const*>());
        case nlohmann::json::value_t::number_integer:
            return value_ptr(
                self.get_ptr<nlohmann::json::number_integer_t const*>());
        case nlohmann::json::value_t::number_unsigned:
            return value_ptr(
                self.get_ptr<nlohmann::json::number_unsigned_t const*>());
        case nlohmann::json::value_t::number_float:
            return value_ptr(
                self.get_ptr<nlohmann::json::number_float_t const*>());
        case nlohmann::json::value_t::string:
            return value_ptr(self.get_ptr<nlohmann::json::string_t const*>());
        case nlohmann::json::value_t::array:
            return value_ptr(self.get_ptr<nlohmann::json::array_t const*>());
        case nlohmann::json::value_t::object:
            return value_ptr(self.get_ptr<nlohmann::json::object_t const*>());
        }
        return value_ptr();
    }
};

struct my_context : std::unordered_map<std::string, bustache::format> {
    using unordered_map::unordered_map;

    bustache::format const* operator()(std::string const& key) const {
        auto it = find(key);
        return it == end() ? nullptr : &it->second;
    }
};

int main() {
    using namespace bustache::literals;

    my_context context{{"EMBEDDED_TEMPLATE", ", goodbye {{goodbyeTag}}"_fmt}};

    nlohmann::json data;
    data["helloTag"] = "Bustache";
    data["goodbyeTag"] = "Mustache";

    bustache::format format(
        "Hello {{helloTag}}{{>EMBEDDED_TEMPLATE}} templating system.\n");

    // Prints: Hello Bustache templating system.
    std::cout << format(data) << "\n";

    // Should print: Hello Bustache, goodbye Mustache templating system.
    auto str = format(data).context(context);
    std::cout << str
              << '\n'; // 'render_ostream': no matching overloaded function
}

HTH

jamboree commented 10 months ago

Also note that using a variable to store the "manipulator" is not recommended.

auto str = format(data).context(context); // it's a manipulator, not a string

Albeit it's fine in your example, it'd cause dangling reference if the data or context is temporary. Instead, consume the manipulator directly:

std::cout << format(data).context(context);
auto str = to_string(format(data).context(context));
luz-arreola commented 10 months ago

That worked so nice, thank you so much! Very slick code, I wouldn't have ever figured it out.