From dadbb856cd95beb938fd3e7bd4e79f35bb309287 Mon Sep 17 00:00:00 2001 From: pantor Date: Sat, 27 Jun 2020 17:47:58 +0200 Subject: [PATCH] add config throw_missing_includes, search_in_files --- README.md | 13 +++++--- include/inja/config.hpp | 8 +++++ include/inja/environment.hpp | 32 ++++++++++++------- include/inja/parser.hpp | 2 +- include/inja/renderer.hpp | 17 +++++++--- single_include/inja/inja.hpp | 60 ++++++++++++++++++++++++++---------- test/unit-files.cpp | 9 ++++++ 7 files changed, 103 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index af79a454..58c9c2fc 100644 --- a/README.md +++ b/README.md @@ -178,17 +178,22 @@ render("{% if not guest_count %}…{% endif %}", data); // True #### Includes -You can either include other template files or already parsed templates. +You can either include other in-memory templates or from the file system. ```.cpp -// Other template files are included relative from the current file location -render("{% include \"footer.html\" %}", data); - // To include in-memory templates, add them to the environment first inja::Template content_template = env.parse("Hello {{ neighbour }}!"); env.include_template("content", content_template); 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); + +// 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. + ### Functions A few functions are implemented within the inja template syntax. They can be called with diff --git a/include/inja/config.hpp b/include/inja/config.hpp index 13f465d1..12dd94a3 100644 --- a/include/inja/config.hpp +++ b/include/inja/config.hpp @@ -50,6 +50,14 @@ struct LexerConfig { */ struct ParserConfig { ElementNotation notation {ElementNotation::Dot}; + bool search_included_templates_in_files {true}; +}; + +/*! + * \brief Class for render configuration. + */ +struct RenderConfig { + bool throw_at_missing_includes {true}; }; } // namespace inja diff --git a/include/inja/environment.hpp b/include/inja/environment.hpp index d1a32aac..aa6058df 100644 --- a/include/inja/environment.hpp +++ b/include/inja/environment.hpp @@ -26,6 +26,16 @@ using json = nlohmann::json; * \brief Class for changing the configuration. */ class Environment { + std::string input_path; + std::string output_path; + + LexerConfig lexer_config; + ParserConfig parser_config; + RenderConfig render_config; + + FunctionStorage function_storage; + TemplateStorage template_storage; + public: Environment() : Environment("") {} @@ -76,6 +86,16 @@ class Environment { parser_config.notation = notation; } + /// Sets the element notation syntax + void set_search_included_templates_in_files(bool search_in_files) { + parser_config.search_included_templates_in_files = search_in_files; + } + + /// Sets whether a missing include will throw an error + void set_throw_at_missing_includes(bool will_throw) { + render_config.throw_at_missing_includes = will_throw; + } + Template parse(nonstd::string_view input) { Parser parser(parser_config, lexer_config, template_storage); return parser.parse(input); @@ -127,7 +147,7 @@ class Environment { } std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) { - Renderer(template_storage, function_storage).render_to(os, tmpl, data); + Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); return os; } @@ -154,16 +174,6 @@ class Environment { void include_template(const std::string &name, const Template &tmpl) { template_storage[name] = tmpl; } - -private: - std::string input_path; - std::string output_path; - - LexerConfig lexer_config; - ParserConfig parser_config; - - FunctionStorage function_storage; - TemplateStorage template_storage; }; /*! diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp index 6f766d59..5da97d8f 100644 --- a/include/inja/parser.hpp +++ b/include/inja/parser.hpp @@ -475,7 +475,7 @@ class Parser { } // sys::path::remove_dots(pathname, true, sys::path::Style::posix); - if (template_storage.find(pathname) == template_storage.end()) { + if (config.search_included_templates_in_files && template_storage.find(pathname) == template_storage.end()) { Template include_template = parse_template(pathname); template_storage.emplace(pathname, include_template); } diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index dea43e99..b4a62c89 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -11,6 +11,7 @@ #include +#include "config.hpp" #include "exceptions.hpp" #include "node.hpp" #include "template.hpp" @@ -176,9 +177,11 @@ class Renderer { std::vector m_tmp_args; json m_tmp_val; + RenderConfig config; + public: - Renderer(const TemplateStorage &included_templates, const FunctionStorage &callbacks) - : template_storage(included_templates), function_storage(callbacks) { + Renderer(const RenderConfig& config, const TemplateStorage &included_templates, const FunctionStorage &callbacks) + : config(config), template_storage(included_templates), function_storage(callbacks) { m_stack.reserve(16); m_tmp_args.reserve(4); m_loop_stack.reserve(16); @@ -471,10 +474,14 @@ class Renderer { break; } case Node::Op::Include: { - auto sub_renderer = Renderer(template_storage, function_storage); + auto sub_renderer = Renderer(config, template_storage, function_storage); auto include_name = get_imm(node)->get_ref(); - auto included_template = template_storage.find(include_name)->second; - sub_renderer.render_to(os, included_template, *m_data, m_loop_data); + auto included_template_it = template_storage.find(include_name); + if (included_template_it != template_storage.end()) { + sub_renderer.render_to(os, included_template_it->second, *m_data, m_loop_data); + } else if (config.throw_at_missing_includes) { + throw_renderer_error("include '" + include_name + "' not found", node); + } break; } case Node::Op::Callback: { diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 20bff7ea..65eda83c 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -1488,6 +1488,14 @@ struct LexerConfig { */ struct ParserConfig { ElementNotation notation {ElementNotation::Dot}; + bool search_included_templates_in_files {true}; +}; + +/*! + * \brief Class for render configuration. + */ +struct RenderConfig { + bool throw_at_missing_includes {true}; }; } // namespace inja @@ -2760,7 +2768,7 @@ class Parser { } // sys::path::remove_dots(pathname, true, sys::path::Style::posix); - if (template_storage.find(pathname) == template_storage.end()) { + if (config.search_included_templates_in_files && template_storage.find(pathname) == template_storage.end()) { Template include_template = parse_template(pathname); template_storage.emplace(pathname, include_template); } @@ -2910,6 +2918,8 @@ class Parser { #include +// #include "config.hpp" + // #include "exceptions.hpp" // #include "node.hpp" @@ -3079,9 +3089,11 @@ class Renderer { std::vector m_tmp_args; json m_tmp_val; + RenderConfig config; + public: - Renderer(const TemplateStorage &included_templates, const FunctionStorage &callbacks) - : template_storage(included_templates), function_storage(callbacks) { + Renderer(const RenderConfig& config, const TemplateStorage &included_templates, const FunctionStorage &callbacks) + : config(config), template_storage(included_templates), function_storage(callbacks) { m_stack.reserve(16); m_tmp_args.reserve(4); m_loop_stack.reserve(16); @@ -3374,10 +3386,14 @@ class Renderer { break; } case Node::Op::Include: { - auto sub_renderer = Renderer(template_storage, function_storage); + auto sub_renderer = Renderer(config, template_storage, function_storage); auto include_name = get_imm(node)->get_ref(); - auto included_template = template_storage.find(include_name)->second; - sub_renderer.render_to(os, included_template, *m_data, m_loop_data); + auto included_template_it = template_storage.find(include_name); + if (included_template_it != template_storage.end()) { + sub_renderer.render_to(os, included_template_it->second, *m_data, m_loop_data); + } else if (config.throw_at_missing_includes) { + throw_renderer_error("include '" + include_name + "' not found", node); + } break; } case Node::Op::Callback: { @@ -3521,6 +3537,16 @@ using json = nlohmann::json; * \brief Class for changing the configuration. */ class Environment { + std::string input_path; + std::string output_path; + + LexerConfig lexer_config; + ParserConfig parser_config; + RenderConfig render_config; + + FunctionStorage function_storage; + TemplateStorage template_storage; + public: Environment() : Environment("") {} @@ -3571,6 +3597,16 @@ class Environment { parser_config.notation = notation; } + /// Sets the element notation syntax + void set_search_included_templates_in_files(bool search_in_files) { + parser_config.search_included_templates_in_files = search_in_files; + } + + /// Sets whether a missing include will throw an error + void set_throw_at_missing_includes(bool will_throw) { + render_config.throw_at_missing_includes = will_throw; + } + Template parse(nonstd::string_view input) { Parser parser(parser_config, lexer_config, template_storage); return parser.parse(input); @@ -3622,7 +3658,7 @@ class Environment { } std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) { - Renderer(template_storage, function_storage).render_to(os, tmpl, data); + Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); return os; } @@ -3649,16 +3685,6 @@ class Environment { void include_template(const std::string &name, const Template &tmpl) { template_storage[name] = tmpl; } - -private: - std::string input_path; - std::string output_path; - - LexerConfig lexer_config; - ParserConfig parser_config; - - FunctionStorage function_storage; - TemplateStorage template_storage; }; /*! diff --git a/test/unit-files.cpp b/test/unit-files.cpp index b30d9d68..e8fa1b03 100644 --- a/test/unit-files.cpp +++ b/test/unit-files.cpp @@ -73,3 +73,12 @@ TEST_CASE("global-path") { CHECK(env_result.load_file("global-path-result.txt") == "Hello Jeff."); } } + +TEST_CASE("include-without-local-files") { + inja::Environment env {test_file_directory}; + env.set_search_included_templates_in_files(false); + + SUBCASE("html") { + CHECK_THROWS_WITH(env.render_file_with_json_file("html/template.txt", "html/data.json"), "[inja.exception.render_error] (at 21:1) include '../test/data/html/header.txt' not found"); + } +}