mirror of
https://github.com/pantor/inja.git
synced 2026-02-17 09:03:58 +00:00
add include callback
This commit is contained in:
@@ -183,12 +183,19 @@ env.render("Content: {% include \"content\" %}", data); // "Content: Hello Peter
|
||||
|
||||
// Other template files are included relative from the current file location
|
||||
render("{% include \"footer.html\" %}", data);
|
||||
```
|
||||
If a corresponding template could not be found in the file system, the *include callback* is called:
|
||||
```
|
||||
// The callback takes the current path and the wanted include name and returns a template
|
||||
env.set_include_callback([&env](const std::string& path, const std::string& name) {
|
||||
return env.parse("Hello {{ name }} from " + name);
|
||||
});
|
||||
|
||||
// You can disable to search for templates in the file system via
|
||||
env.set_search_included_templates_in_files(false);
|
||||
```
|
||||
|
||||
Inja will throw an `inja::RenderError` if an included file is not found. To disable this error, you can call `env.set_throw_at_missing_includes(false)`.
|
||||
Inja will throw an `inja::RenderError` if an included file is not found and no callback is specified. To disable this error, you can call `env.set_throw_at_missing_includes(false)`.
|
||||
|
||||
#### Assignments
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "string_view.hpp"
|
||||
#include "template.hpp"
|
||||
|
||||
namespace inja {
|
||||
|
||||
@@ -65,6 +66,8 @@ struct LexerConfig {
|
||||
*/
|
||||
struct ParserConfig {
|
||||
bool search_included_templates_in_files {true};
|
||||
|
||||
std::function<Template(const std::string&, const std::string&)> include_callback;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -161,7 +161,10 @@ public:
|
||||
|
||||
json load_json(const std::string &filename) {
|
||||
std::ifstream file;
|
||||
open_file_or_throw(input_path + filename, file);
|
||||
file.open(input_path + filename);
|
||||
if (file.fail()) {
|
||||
INJA_THROW(FileError("failed accessing file at '" + input_path + filename + "'"));
|
||||
}
|
||||
json j;
|
||||
file >> j;
|
||||
return j;
|
||||
@@ -202,6 +205,13 @@ public:
|
||||
void include_template(const std::string &name, const Template &tmpl) {
|
||||
template_storage[name] = tmpl;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Sets a function that is called when an included file is not found
|
||||
*/
|
||||
void set_include_callback(const std::function<Template(const std::string&, const std::string&)>& callback) {
|
||||
parser_config.include_callback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "function_storage.hpp"
|
||||
#include "string_view.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
@@ -86,19 +86,43 @@ class Parser {
|
||||
}
|
||||
|
||||
void add_to_template_storage(nonstd::string_view path, std::string& template_name) {
|
||||
if (config.search_included_templates_in_files && template_storage.find(template_name) == template_storage.end()) {
|
||||
if (template_storage.find(template_name) != template_storage.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string original_path = static_cast<std::string>(path);
|
||||
std::string original_name = template_name;
|
||||
|
||||
if (config.search_included_templates_in_files) {
|
||||
// Build the relative path
|
||||
template_name = static_cast<std::string>(path) + template_name;
|
||||
template_name = original_path + original_name;
|
||||
if (template_name.compare(0, 2, "./") == 0) {
|
||||
template_name.erase(0, 2);
|
||||
}
|
||||
|
||||
if (template_storage.find(template_name) == template_storage.end()) {
|
||||
auto include_template = Template(load_file(template_name));
|
||||
template_storage.emplace(template_name, include_template);
|
||||
parse_into_template(template_storage[template_name], template_name);
|
||||
// Load file
|
||||
std::ifstream file;
|
||||
file.open(template_name);
|
||||
if (!file.fail()) {
|
||||
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
|
||||
auto include_template = Template(text);
|
||||
template_storage.emplace(template_name, include_template);
|
||||
parse_into_template(template_storage[template_name], template_name);
|
||||
return;
|
||||
|
||||
} else if (!config.include_callback) {
|
||||
INJA_THROW(FileError("failed accessing file at '" + template_name + "'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try include callback
|
||||
if (config.include_callback) {
|
||||
auto include_template = config.include_callback(original_path, original_name);
|
||||
template_storage.emplace(template_name, include_template);
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_expression(Template &tmpl, Token::Kind closing) {
|
||||
@@ -632,9 +656,12 @@ public:
|
||||
sub_parser.parse_into(tmpl, path);
|
||||
}
|
||||
|
||||
std::string load_file(nonstd::string_view filename) {
|
||||
std::string load_file(const std::string& filename) {
|
||||
std::ifstream file;
|
||||
open_file_or_throw(static_cast<std::string>(filename), file);
|
||||
file.open(filename);
|
||||
if (file.fail()) {
|
||||
INJA_THROW(FileError("failed accessing file at '" + filename + "'"));
|
||||
}
|
||||
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -11,19 +11,6 @@
|
||||
|
||||
namespace inja {
|
||||
|
||||
inline void open_file_or_throw(const std::string &path, std::ifstream &file) {
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
#ifndef INJA_NOEXCEPTION
|
||||
try {
|
||||
file.open(path);
|
||||
} catch (const std::ios_base::failure & /*e*/) {
|
||||
INJA_THROW(FileError("failed accessing file at '" + path + "'"));
|
||||
}
|
||||
#else
|
||||
file.open(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace string_view {
|
||||
inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) {
|
||||
start = std::min(start, view.size());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -185,6 +185,29 @@ TEST_CASE("templates") {
|
||||
"[inja.exception.file_error] failed accessing file at 'does-not-exist'");
|
||||
}
|
||||
|
||||
SUBCASE("include-callback") {
|
||||
inja::Environment env;
|
||||
|
||||
CHECK_THROWS_WITH(env.parse("{% include \"does-not-exist\" %}!"),
|
||||
"[inja.exception.file_error] failed accessing file at 'does-not-exist'");
|
||||
|
||||
env.set_search_included_templates_in_files(false);
|
||||
env.set_include_callback([&env](const std::string&, const std::string&) {
|
||||
return env.parse("Hello {{ name }}");
|
||||
});
|
||||
|
||||
inja::Template t1 = env.parse("{% include \"greeting\" %}!");
|
||||
CHECK(env.render(t1, data) == "Hello Peter!");
|
||||
|
||||
env.set_search_included_templates_in_files(true);
|
||||
env.set_include_callback([&env](const std::string&, const std::string& name) {
|
||||
return env.parse("Bye " + name);
|
||||
});
|
||||
|
||||
inja::Template t2 = env.parse("{% include \"Jeff\" %}!");
|
||||
CHECK(env.render(t2, data) == "Bye Jeff!");
|
||||
}
|
||||
|
||||
SUBCASE("include-in-loop") {
|
||||
inja::json loop_data;
|
||||
loop_data["cities"] = inja::json::array({{{"name", "Munich"}}, {{"name", "New York"}}});
|
||||
|
||||
Reference in New Issue
Block a user