Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mustache lambda support #299

Merged
merged 9 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/guides/templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ The HTML page (including the mustache tags). It is usually loaded into `crow::mu
For more information on how to formulate a template, see [this mustache manual](http://mustache.github.io/mustache.5.html).

### Context
A JSON object containing the tags as keys and their values. `crow::mustache::context` is actually a [crow::json::wvalue](../json#wvalue).
A JSON object containing the tags as keys and their values. `crow::mustache::context` is actually a [crow::json::wvalue](../json#wvalue).<br><br>

!!! note

`crow::mustache::context` can take a C++ lambda as a value. The lambda needs to take a string as an argument and return a string, such as `#!cpp ctx[lmd] = [&](std::string){return "Hello World";};`.

## Returning a template
To return a mustache template, you need to load a page using `#!cpp auto page = crow::mustache::load("path/to/template.html");`, keep in mind that the path is relative to the templates directory.<br>
Expand Down
12 changes: 12 additions & 0 deletions examples/example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ int main()
res.end();
});

//same as the example above but uses mustache instead of stringstream
CROW_ROUTE(app, "/add_mustache/<int>/<int>")
([](int a, int b) {
auto t = crow::mustache::compile("Value is {{func}}");
crow::mustache::context ctx;
ctx["func"] = [&](std::string) {
return std::to_string(a + b);
};
auto result = t.render(ctx);
return result;
});

// Compile error with message "Handler type is mismatched with URL paramters"
//CROW_ROUTE(app,"/another/<int>")
//([](int a, int b){
Expand Down
36 changes: 32 additions & 4 deletions include/crow/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ namespace crow
String,
List,
Object,
Function
};

inline const char* get_type_str(type t)
Expand All @@ -99,6 +100,7 @@ namespace crow
case type::List: return "List";
case type::String: return "String";
case type::Object: return "Object";
case type::Function: return "Function";
default: return "Unknown";
}
}
Expand Down Expand Up @@ -766,6 +768,7 @@ namespace crow
os << '}';
}
break;
case type::Function: os << "custom function"; break;
}
return os;
}
Expand Down Expand Up @@ -1259,10 +1262,11 @@ namespace crow
si(value) {}
constexpr number(double value) noexcept:
d(value) {}
} num; ///< Value if type is a number.
std::string s; ///< Value if type is a string.
std::unique_ptr<list> l; ///< Value if type is a list.
std::unique_ptr<object> o; ///< Value if type is a JSON object.
} num; ///< Value if type is a number.
std::string s; ///< Value if type is a string.
std::unique_ptr<list> l; ///< Value if type is a list.
std::unique_ptr<object> o; ///< Value if type is a JSON object.
std::function<std::string(std::string&)> f; ///< Value if type is a function (C++ lambda)

public:
wvalue():
Expand Down Expand Up @@ -1342,6 +1346,7 @@ namespace crow
case type::Null:
case type::False:
case type::True:
case type::Function:
return;
case type::Number:
nt = r.nt();
Expand Down Expand Up @@ -1401,6 +1406,8 @@ namespace crow
o = std::unique_ptr<object>(new object{});
o->insert(r.o->begin(), r.o->end());
return;
case type::Function:
f = r.f;
}
}

Expand Down Expand Up @@ -1633,6 +1640,14 @@ namespace crow
return *this;
}

wvalue& operator=(std::function<std::string(std::string&)>&& func)
{
reset();
t_ = type::Function;
f = std::move(func);
return *this;
}

wvalue& operator[](unsigned index)
{
if (t_ != type::List)
Expand Down Expand Up @@ -1676,6 +1691,13 @@ namespace crow
return result;
}

std::string execute(std::string txt = "") const //Not using reference because it cannot be used with a default rvalue
{
if (t_ != type::Function)
return "";
return f(txt);
}

/// If the wvalue is a list, it returns the length of the list, otherwise it returns 1.
std::size_t size() const
{
Expand Down Expand Up @@ -1721,6 +1743,8 @@ namespace crow
}
return sum + 2;
}
case type::Function:
return 0;
}
return 1;
}
Expand Down Expand Up @@ -1847,6 +1871,10 @@ namespace crow
out.push_back('}');
}
break;

case type::Function:
out += "custom function";
break;
}
}

Expand Down
15 changes: 15 additions & 0 deletions include/crow/mustache.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,21 @@ namespace crow
else
out += ctx.s;
break;
case json::type::Function:
{
std::string execute_result = ctx.execute();
while (execute_result.find("{{") != std::string::npos)
{
template_t result_plug(execute_result);
execute_result = result_plug.render(*(stack[0]));
}

if (action.t == ActionType::Tag)
escape(execute_result, out);
else
out += execute_result;
}
break;
default:
throw std::runtime_error("not implemented tag type" + boost::lexical_cast<std::string>(static_cast<int>(ctx.t())));
}
Expand Down
10 changes: 10 additions & 0 deletions tests/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,16 @@ TEST_CASE("template_basic")
CHECK("attack of killer tomatoes" == result);
} // template_basic

TEST_CASE("template_function")
{
auto t = crow::mustache::compile("attack of {{func}}");
crow::mustache::context ctx;
ctx["name"] = "killer tomatoes";
ctx["func"] = [&](std::string){return std::string("{{name}}, IN SPACE!");};
auto result = t.render(ctx);
CHECK("attack of killer tomatoes, IN SPACE!" == result);
}

TEST_CASE("template_load")
{
crow::mustache::set_base(".");
Expand Down