kainjow / Mustache

Mustache text templates for modern C++
Boost Software License 1.0
357 stars 49 forks source link

Help with reading partials #34

Closed KingDuckZ closed 5 years ago

KingDuckZ commented 5 years ago

I'm trying to use the {{>filename}} mustache syntax but it always results in a blank in the generated output. Reading the test code in this repository I seem to understand that I need to register all the filenames beforehand, for example:

    dat["filename"] = partial([](){
        std::ifstream all_data("filename.mstch");
        all_data >> std::noskipws;
        return std::string(
            std::istream_iterator<uint8_t>(all_data),
            std::istream_iterator<uint8_t>()
        );
    };

While it's fine with me to provide such a function, I don't see how I could foresee all the possible filenames the end user could come up with. How can I deal with the generic case of users wanting to do {{>filename}} one day and {{>donaldduck}} the next?

KingDuckZ commented 5 years ago

I was having a look at the code to see if I could figure out how to do this by myself and I spotted this dodgy line. Isn't that taking a reference to a temporary when a partial is invoked? It looks like this code is not correct to me.

KingDuckZ commented 5 years ago

I don't think I've seen support for this so I just added a quick implementation that works for me:

    From 8b4e20256cc9da7272a2ccce213a286720e909db Mon Sep 17 00:00:00 2001
    From: King_DuckZ <king_duckz@gmx.com>
    Date: Thu, 2 May 2019 16:38:01 +0100
    Subject: [PATCH] Add support for functors that load generic partials.

    ---
     mustache.hpp | 47 ++++++++++++++++++++++++++++++++++++-----------
     1 file changed, 36 insertions(+), 11 deletions(-)

    diff --git a/mustache.hpp b/mustache.hpp
    index ee88f9c..bb77345 100644
    --- a/mustache.hpp
    +++ b/mustache.hpp
    @@ -38,6 +38,7 @@
     #include <sstream>
     #include <unordered_map>
     #include <vector>
    +#include <string>

     namespace kainjow {
     namespace mustache {
    @@ -431,15 +432,22 @@ public:

         virtual const basic_data<string_type>* get(const string_type& name) const = 0;
         virtual const basic_data<string_type>* get_partial(const string_type& name) const = 0;
    +    virtual const basic_lambda<string_type>* get_partial() const = 0;
     };

     template <typename string_type>
     class context : public basic_context<string_type> {
     public:
    -    context(const basic_data<string_type>* data) {
    +    explicit context(const basic_data<string_type>* data) {
             push(data);
         }

    +    context(const basic_data<string_type>* data, const basic_lambda<string_type>& general_partial) :
    +        context(data)
    +    {
    +        general_partial_ = general_partial;
    +    }
    +
         context() {
         }

    @@ -493,11 +501,16 @@ public:
             return nullptr;
         }

    +    virtual const basic_lambda<string_type>* get_partial() const override {
    +        return &general_partial_;
    +    }
    +
         context(const context&) = delete;
         context& operator= (const context&) = delete;

     private:
         std::vector<const basic_data<string_type>*> items_;
    +    basic_lambda<string_type> general_partial_;
     };

     template <typename string_type>
    @@ -900,21 +913,21 @@ public:
         }

         template <typename stream_type>
    -    stream_type& render(const basic_data<string_type>& data, stream_type& stream) {
    -        render(data, [&stream](const string_type& str) {
    +    stream_type& render(const basic_data<string_type>& data, const basic_lambda<string_type>& partial, stream_type& stream) {
    +        render(data, partial, [&stream](const string_type& str) {
                 stream << str;
             });
             return stream;
         }

    -    string_type render(const basic_data<string_type>& data) {
    +    string_type render(const basic_data<string_type>& data, const basic_lambda<string_type>& partial) {
             std::basic_ostringstream<typename string_type::value_type> ss;
    -        return render(data, ss).str();
    +        return render(data, partial, ss).str();
         }

         template <typename stream_type>
    -    stream_type& render(basic_context<string_type>& ctx, stream_type& stream) {
    -        context_internal<string_type> context{ctx};
    +    stream_type& render(basic_context<string_type>& ctx, const basic_lambda<string_type>& partial, stream_type& stream) {
    +        context_internal<string_type> context{ctx, partial};
             render([&stream](const string_type& str) {
                 stream << str;
             }, context);
    @@ -923,15 +936,15 @@ public:

         string_type render(basic_context<string_type>& ctx) {
             std::basic_ostringstream<typename string_type::value_type> ss;
    -        return render(ctx, ss).str();
    +        return render(ctx, basic_lambda<string_type>(), ss).str();
         }

         using render_handler = std::function<void(const string_type&)>;
    -    void render(const basic_data<string_type>& data, const render_handler& handler) {
    +    void render(const basic_data<string_type>& data, const basic_lambda<string_type>& partial, const render_handler& handler) {
             if (!is_valid()) {
                 return;
             }
    -        context<string_type> ctx{&data};
    +        context<string_type> ctx{&data, partial};
             context_internal<string_type> context{ctx};
             render(handler, context);
         }
    @@ -1024,8 +1037,19 @@ private:
                     }
                     return component<string_type>::walk_control::skip;
                 case tag_type::partial:
    +            {
    +                string_type partial_result;
                     if ((var = ctx.ctx.get_partial(tag.name)) != nullptr && (var->is_partial() || var->is_string())) {
    -                    const auto& partial_result = var->is_partial() ? var->partial_value()() : var->string_value();
    +                    partial_result = var->is_partial() ? var->partial_value()() : var->string_value();
    +                }
    +                else {
    +                    auto* const p = ctx.ctx.get_partial();
    +                    if (p && *p) {
    +                        partial_result = (*p)(tag.name);
    +                    }
    +                }
    +
    +                if (!partial_result.empty()) {
                         basic_mustache tmpl{partial_result};
                         tmpl.set_custom_escape(escape_);
                         if (!tmpl.is_valid()) {
    @@ -1041,6 +1065,7 @@ private:
                         }
                     }
                     break;
    +            }
                 case tag_type::set_delimiter:
                     ctx.delim_set = *comp.tag.delim_set;
                     break;
    -- 
    2.21.0

I didn't run any tests or tried any combination other than the one I needed, nor I have any idea if adding the new functor to context is the right thing to do or not. So please review it thoroughly :)

KingDuckZ commented 5 years ago

Any help with this please? @kainjow?

kainjow commented 5 years ago

Sorry for the delay.

I think the existing partial support in the context class supports what you want to do? See https://github.com/kainjow/Mustache/blob/master/tests.cpp#L1174