view templates/templater.cpp @ 0:a4671277546c tip

created the repository for the thymian project
author ferencd
date Tue, 17 Aug 2021 11:19:54 +0200
parents
children
line wrap: on
line source
#include "templater.h"
#include "dictionary.h"

#ifdef PYTHON_SCRIPTING
#include "python_runner.h"
#endif

#include <algorithm>
#include <filesystem>

#include <log.h>
#include <cwctype>


void templater_base::resolve_all_includes(std::string& templatized, bool do_replace)
{
    size_t inc_pos = templatized.find(INCLUDE_TAG);
    while(inc_pos != std::string::npos)
    {
        templatized = resolve_includes(templatized, inc_pos, do_replace);

        // see if we have pulled in some extra vars
        if(do_replace)
        {
            templatized = stringholder(templatized).templatize(kps).get();
        }

        inc_pos = templatized.find(INCLUDE_TAG);
    }
}

std::string templater_base::get(const std::string &template_name)
{
    // Getting the content
    std::string content = template_warehouse::instance().getTemplateContent(template_name);

    // Resolve the #define's. This will update the extra_kp's
    std::string content_without_vars = resolve_defines(content);

    // resolve the initializer scripts, since they may modify the kps
    std::string scripts_resolved = resolve_scripts(content_without_vars, INIT_SCRIPT_TAG);

    // resolve all the top level template arguments
    std::string templatized = stringholder(scripts_resolved).templatize(kps).get();

    // then search for all the {#include 's and: templatize those with the given variable: Value pairs
    // and include them here
    bool done = false;
    while(!done)
    {
        // search for structure definitions, there can be more than one
        size_t struct_pos = templatized.find(STRUCT_TAG);
        while(struct_pos != std::string::npos)
        {
            templatized = resolve_structure_declaration(struct_pos, templatized);
            struct_pos = templatized.find(STRUCT_TAG);
        }

        // search for input data definition hints
        size_t parameters_pos = templatized.find(PARAMETERS_TAG);
        if(parameters_pos != std::string::npos)
        {
            templatized = resolve_parameters(parameters_pos, templatized);
        }

        // search for the "include"'s
        resolve_all_includes(templatized);

        // now search for the "if"'s. Remember, ifs cannot have ifs in their body
        size_t if_pos = templatized.find(IF_TAG);
        if(if_pos != std::string::npos)
        {
            while(if_pos != std::string::npos)
            {
                templatized = resolve_ifs(if_pos, templatized);
                if_pos = templatized.find(IF_TAG);
            }
        }
        else
        {
            done = true;
        }
    }

    return templatized;
}

std::string templater_base::extract_identifier_word(const std::string& input, size_t& i, std::vector<char> separators,
                                                    std::set<char> extra_allowed_chars, char&& c)
{
    skip_whitespace(input, i);

    std::string result = "";
    while(i < input.length() && (input.at(i) == '_' ||
          extra_allowed_chars.count(input.at(i)) ||
          isalnum(input.at(i))) )
    {
        if(separators.empty())
        {
            result += input.at(i);
            i++;
        }
        else
        {
            if(find(separators.begin(), separators.end(), input.at(i)) == separators.end())
            {
                result += input.at(i);
                i++;
            }
            else
            {
                if(c != 0)
                {
                    c = input[i];
                }
                i ++; // skip the actual separator
                break;
            }
        }
    }

    skip_whitespace(input, i);
    if(!separators.empty())
    {
        if(find(separators.begin(), separators.end(), input.at(i)) != separators.end())
        {
            if(c != 0)
            {
                c = input[i];
            }
            i++; // this characater is a separator. Skip it. And the space after
            skip_whitespace(input, i);
        }
    }

    return result;
}

bool templater_base::check_opening_parenthesis(const std::string& input, size_t& i)
{
    bool opening_parentheses = false;
    // skip the parenthesis if any
    if(i < input.length() && input.at(i) == '(')
    {
        i ++;
        skip_whitespace(input, i);
        opening_parentheses = true;
    }

    return opening_parentheses;
}

bool templater_base::check_closing_comment(const std::string& templatized, size_t& i, std::size_t& include_tag_end_position)
{
    if(i + 3 < templatized.length())
    {
        if(templatized[i+1] == '-' && templatized[i+2] == '-' && templatized[i + 3] == '>')
        {
            include_tag_end_position = i + 4;
            return true;
        }
    }
    return false;
}

std::string templater_base::get_error() const
{
    return error;
}

std::vector<std::string> templater_base::variables(bool resolve_includes_too)
{
    std::vector<std::string> result;

    std::string content = template_warehouse::instance().getTemplateContent(name());
    std::string::size_type start_pos = 0;

    if(resolve_includes_too)
    {
        resolve_all_includes(content, false);
    }

    while((start_pos = content.find(TEMPLATE_VARIABLE_START_DELIMITER, start_pos)) != std::string::npos)
    {
        std::string current_varname = "";
        start_pos += TEMPLATE_VARIABLE_START_DELIMITER.length();
        while(start_pos < content.length() && content[start_pos] != '}')
        {
            current_varname += content[start_pos++];
        }
        if(start_pos == content.length())
        {
            set_error("Unterminated variable name");
            return result;
        }
        result.push_back(current_varname);
        start_pos ++; // to skip the closing '}'
    }

    return result;
}

std::string templater_base::resolve_includes(std::string templatized, size_t inc_pos, bool do_variable_replacement)
{
    std::map<std::string, std::string> local_pairs;
    std::size_t include_tag_end_position = std::string::npos;
    std::size_t include_tag_start_position = inc_pos;

    inc_pos += INCLUDE_TAG.length();

    std::string inc_template = extract_identifier_word(templatized, inc_pos);
    bool opening_parentheses = check_opening_parenthesis(templatized, inc_pos);
    size_t i = inc_pos;
    bool closing_comment_found = false;

    while(i < templatized.length() && ! closing_comment_found)
    {
        std::string var_name = extract_identifier_word(templatized, i, {':','='});
        if(i == templatized.length() || var_name.empty())
        {
            if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment
            {
                check_closing_comment(templatized, i, include_tag_end_position);
            }

            break;
        }

        // now read the variable value
        std::string var_value = "";
        bool var_value_read = false;
        while(!var_value_read && i < templatized.length())
        {
            if(templatized[i] == '"') // string starts. Do not read the '"'
            {
                i++; // skip the '"'

                bool string_read = false;
                while(i < templatized.length() && !string_read)
                {
                    if(templatized[i] == '\\') // Skip the backslash from the escape sequence
                    {
                        i++;
                    }

                    var_value += templatized[i++];

                    if(i == templatized.length())
                    {
                        set_error("String literal is not finished");
                        break;
                    }

                    if(templatized[i] == '"' && templatized[i-1] != '\\')
                    {
                        string_read = true;
                    }
                }

                if(i == templatized.length()) // invalid syntax
                {
                    set_error("Invalid syntax, could not resolve an include");
                    break;
                }

                i++; // skip closing quote
            }
            else
            {
                // see if this is a closing parenthesis, but only if there was an opening one
                if(opening_parentheses && templatized[i] == ')')
                {
                    var_value_read = true;

                    // now see if there is a closing comment. Everything after it will be ignored
                    while(i < templatized.length() && !closing_comment_found)
                    {
                        i ++;
                        skip_whitespace(templatized, i);
                        if(i < templatized.length() && templatized[i] == '#') // This should be the closing comment
                        {
                            closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
                        }
                    }
                }

                if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment
                {
                    closing_comment_found = var_value_read = check_closing_comment(templatized, i, include_tag_end_position);
                }

                if(templatized[i] == '{' && i<templatized.length() && templatized[i + 1] == '#') // The substitution of a variable with another one from this level
                {
                    // Read in the entire variable and simply change the var_value to the one in our objects' map
                    i+=2; // skip {#
                    std::string upperlevel_var_name = extract_identifier_word(templatized, i, {'}'});

                    if(i == templatized.length() || upperlevel_var_name.empty())
                    {
                        break;
                    }

                    if(kps.count(upperlevel_var_name) != 0)
                    {
                        var_value += kps[upperlevel_var_name];
                    }
                }

                // and comma (or space) separatin the variables?
                if(i < templatized.length() &&  (templatized[i] == ',' || iswspace(templatized[i])) )
                {
                    var_value_read = true;
                    i++;
                    skip_whitespace(templatized, i);
                }

                // still not read the value?
                if(i < templatized.length() && !var_value_read)
                {
                    var_value += templatized[i++];
                }
            }
        }

        skip_whitespace(templatized, i);

        // now insert into local_pairs the keys and values
        local_pairs.insert(make_pair(var_name, var_value));
    }

    // and now read in the template from inc_template
    // and replace everything that is supposed to be replaced
    std::string inc_content = template_warehouse::instance().getTemplateContent( inc_template );
    std::string inc_templatized = do_variable_replacement ? stringholder(inc_content).templatize(local_pairs).get() : inc_content;

    templatized.erase(include_tag_start_position, include_tag_end_position - include_tag_start_position);
    templatized.insert(include_tag_start_position, inc_templatized);
    return templatized;
}

std::string templater_base::resolve_structure_declaration(size_t struct_pos, std::string templatized)
{
    std::size_t include_tag_start_position = struct_pos;

    struct_pos += STRUCT_TAG.length();

    std::string struct_name = extract_identifier_word(templatized, struct_pos);
    bool opening_parentheses = check_opening_parenthesis(templatized, struct_pos);

    add_structure_decl(struct_name, struct_name);

    // and now read in the structure members' names
    size_t i = struct_pos;
    bool closing_comment_found = false;
    std::size_t include_tag_end_position = std::string::npos;

    while (i < templatized.length() && !closing_comment_found)
    {
        std::string member_name = "";
        while(i < templatized.length() && (isalnum(templatized[i]) || templatized[i] == '_'))
        {
            member_name += templatized[i++];
        }
        if(member_name.empty()) // syntax error, just give back what came out
        {
            set_error("Invalid syntax, could not resolve a structure declaration");
            return templatized;
        }

        skip_whitespace(templatized, i);

        // var_name now is the name of the structure's member, make it
        // a default value
        get_structure(struct_name)[member_name] = "";

        // now this should point either to a space or a ","
        i++;

        // se if we have a "," coming up
        if(templatized[i] == ',')
        {
            i ++;
            skip_whitespace(templatized, i);
        }

        // see if this is a closing parenthesis, but only if there was an opening one
        if(opening_parentheses && templatized[i] == ')')
        {

            // now see if there is a closing comment. Everything after it will be ignored
            while(i < templatized.length() && !closing_comment_found)
            {
                i ++;
                skip_whitespace(templatized, i);
                if(i < templatized.length() && templatized[i] == '#') // This should be the closing comment
                {
                    closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
                }
            }
        }

        if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment
        {
            closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
        }
    }

    templatized.erase(include_tag_start_position, include_tag_end_position - include_tag_start_position);
    return templatized;
}

templater_base &templater_base::templatize(const template_struct &s)
{
    precalculated = "";
    std::string templatized = get(); // will initialize the parameters
    if(!m_parameters.empty())
    {
		for(const auto &p : m_parameters)
        {
            if(p.first == s.name)
            {
				for(const auto &x : s.struct_members)
                {
                    std::string fullname = s.name + "." + x.first;
                    kps.insert(make_pair(fullname, unafrog::utils::to_string(x.second)));
                }
            }
        }
    }
    templatized = stringholder(templatized).templatize(kps).get();
    templatized = resolve_ifeqs(templatized);
    templatized = resolve_scripts(templatized, SCRIPT_TAG);
    precalculated = templatized;

    return *this;
}

std::string templater_base::resolve_loops(std::string templatized, const template_vector_par &v)
{
    size_t loop_pos = templatized.find(LOOP_TAG);
    while(loop_pos != std::string::npos)
    {
        // find the stuff between the iterate and end iterate tags
        std::size_t loop_tag_end_position = std::string::npos;
        std::size_t loop_tag_start_position = loop_pos;

        loop_pos += LOOP_TAG.length();

        // see on what are we iterating through
        std::string loop_target = extract_identifier_word(templatized, loop_pos);

        // there should be nothing more after this
        if(!check_closing_comment(templatized, loop_pos, loop_tag_end_position))
        {
            return templatized;
        }

        // now find the end iterate tag
        size_t end_loop_pos = templatized.find(ENDLOOP_TAG, loop_tag_end_position);
        size_t save_endpos = end_loop_pos;
        end_loop_pos += ENDLOOP_TAG.length();
        size_t endloop_endpos = std::string::npos;

        size_t temp = end_loop_pos;

        while(temp != std::string::npos)
        {
            size_t unused = temp;

            std::string endloop_target = extract_identifier_word(templatized, unused);
            if(endloop_target == loop_target)
            {
                if(!check_closing_comment(templatized, unused, endloop_endpos))
                {
                    return templatized;
                }

                break;
            }

            temp = templatized.find(ENDLOOP_TAG, temp);
            save_endpos = temp;
            temp += ENDLOOP_TAG.length();
        }

        // now between loop_tag_end_position and save_endpos there is the strign we need
        std::string loop_content = templatized.substr(loop_tag_end_position, save_endpos - loop_tag_end_position);

        std::string final_loop_content = "";

        // and now insert a bunch of kps for the elements in v
        if(!m_parameters.empty())
        {
            size_t c = 0;
			for(const auto &ts : v.value())
            {
                c++;
                for(const auto& p : m_parameters)
                {
                    if(p.first == v.name())
                    {
                        stringholder sh(loop_content);

						for(const auto &x : ts.struct_members)
                        {
                            //if(x.first == loop_target)
                            {
                                std::string fullname = v.name() + "." + x.first;
                                std::string fullname_to_replace_with = v.name() + "." + x.first + ":" + std::to_string(c);

                                kps.insert(make_pair(fullname_to_replace_with, unafrog::utils::to_string(x.second)));
                                sh.replace_all(TEMPLATE_VARIABLE_START_DELIMITER + fullname +TEMPLATE_VARIABLE_END_DELIMITER,
                                              TEMPLATE_VARIABLE_START_DELIMITER + fullname_to_replace_with + TEMPLATE_VARIABLE_END_DELIMITER);
                            }
                        }

                        final_loop_content += sh.get();
                    }
                }
            }
        }
        // now fetch out the substring (loop_tag_end_position, save_endpos - loop_tag_end_position) from templatized
        // and replace with final_loop_content

        if(v.name() == loop_target)
        {
            std::string part1 = templatized.substr(0, loop_tag_end_position);

            part1.erase(loop_tag_start_position, loop_tag_end_position - loop_tag_start_position);
            part1 += final_loop_content;
            part1 += templatized.substr(endloop_endpos);

            templatized = part1;
            loop_pos = templatized.find(LOOP_TAG);
        }
        else
        {
            loop_pos = templatized.find(LOOP_TAG, endloop_endpos);
        }
    }

    templatized = stringholder(templatized).templatize(kps).get();
    precalculated = templatized;
    return templatized;
}

std::string templater_base::resolve_ifeqs(std::string templatized)
{
    bool done = false;
    while(!done)
    {
        size_t ifeq_pos = templatized.find(IFEQ_TAG);
        if(ifeq_pos != std::string::npos)
        {
            while(ifeq_pos != std::string::npos)
            {
                templatized = resolve_ifeq(ifeq_pos, templatized);
                ifeq_pos = templatized.find(IFEQ_TAG);
            }
        }
        else
        {
            done = true;
        }
    }
    return templatized;
}

std::string templater_base::resolve_translations(std::string templatized, const std::string &target_language, bool generate_span, std::map<std::string, std::map<std::string, std::string> >& translations)
{
    bool done = false;
    while(!done)
    {
        size_t ifeq_pos = templatized.find(TRANSLATE_TAG);
        if(ifeq_pos != std::string::npos)
        {
            while(ifeq_pos != std::string::npos)
            {
                std::map<std::string, std::string> local_translations;
                std::string span_id;

                templatized = resolve_translation(ifeq_pos, templatized, target_language, generate_span, span_id, local_translations);

                translations[span_id] = local_translations;

                ifeq_pos = templatized.find(TRANSLATE_TAG);
            }
        }
        else
        {
            done = true;
        }
    }
    return templatized;

}

static std::string span_name(std::string str)
{
    str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end());
    return "span_" + str;
}

std::string templater_base::resolve_translation(size_t tr_pos, std::string templatized, const std::string &target_language, bool generate_span, std::string& span_id, std::map<std::string, std::string> &translations)
{
    size_t i = tr_pos;

    // skip the <!--#translate
    i += TRANSLATE_TAG.length();

    // and any whitespace that might follow
    skip_whitespace(templatized, i);
    size_t translateable_start = i;

    // now extract the word to translate
    if(i < templatized.length())
    {
        char c_sep = 32;
        std::string what_to = extract_identifier_word(templatized, i, {'#'}, {' ', ',', '.', '!', '?', ':', ':'}, std::move(c_sep));
        skip_whitespace(templatized, i);

        std::string before_translate = templatized.substr(0, tr_pos);
        std::string between = templatized.substr(translateable_start, what_to.length());
        std::string after_translate = templatized.substr(translateable_start + what_to.length() + 4);

        span_id = span_name(between);

        templatized =  before_translate + (generate_span ? "<span id='" + span_id + "'>" : "") + dictionary::translate(between, target_language, true, translations) +(generate_span ? "</span>" : "")  + after_translate;
    }


    return templatized;

}

templater_base &templater_base::templatize(const template_vector_par &v)
{
    do_not_resolve_in_get();
    std::string templatized = precalculated.empty() ? get() : precalculated;

    do_resolve_in_get();

    templatized = resolve_loops(templatized, v);
    templatized = resolve_ifeqs(templatized);
    templatized = resolve_scripts(templatized, SCRIPT_TAG);
    precalculated = templatized;

    return *this;
}

void templater_base::skip_whitespace(const std::string& templatized, size_t& i)
{
    while(i < templatized.length() && std::iswspace(templatized[i]))
    {
        i++;
    }
}

std::string templater_base::resolve_parameters(size_t parameters_pos, std::string templatized)
{
    std::size_t include_tag_end_position = std::string::npos;
    std::size_t include_tag_start_position = parameters_pos;

    parameters_pos += PARAMETERS_TAG.length();
    skip_whitespace(templatized, parameters_pos);

    size_t i = parameters_pos;
    bool closing_comment_found = false;
    while(i < templatized.length() && ! closing_comment_found)
    {
        // first: read in the variable name
        char c_sep = 32;
        std::string var_name = extract_identifier_word(templatized, i, {':', ' '}, {}, static_cast<char&&>(c_sep));

        if(i == templatized.length() || var_name.empty())
        {
            return templatized;
        }

        std::string var_type = "";
        bool iterable = false;

        if(c_sep == ':')
        {
            // now read the variable type
            var_type = extract_identifier_word(templatized, i);

            if(i < templatized.length() && templatized[i] == '[')
            {
                i ++;
                skip_whitespace(templatized, i);
                if(i < templatized.length() &&  templatized[i] == ']')
                {
                    i ++;
                }
                iterable = true;
            }
        }

        add_parameter(var_name, var_type, iterable);

        skip_whitespace(templatized, i);

        // se if we have a "," coming up
        if(templatized[i] == ',')
        {
            i ++;
            skip_whitespace(templatized, i);
        }

        if(templatized[i] == '#') // This should be the closing comment
        {
            closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
        }
    }

    templatized.erase(include_tag_start_position, include_tag_end_position - include_tag_start_position);
    return templatized;
}

std::string templater_base::resolve_ifs(size_t if_pos, std::string templatized)
{
    size_t i = if_pos;

    // skip the <!--#if
    i += IF_TAG.length();

    // and any whitespace that might follow
    skip_whitespace(templatized, i);

    // now extract the variable
    if(i < templatized.length())
    {
        char c_sep = 32;
        std::string var_name = extract_identifier_word(templatized, i, {' ', '#'}, {}, std::move(c_sep));
        skip_whitespace(templatized, i);

        std::string var_value = "";
        // now if var_name is empty remove the if and it's body.
        if(kps.count(var_name))
        {
            var_value = kps[var_name];
        }

        std::string closing_endif_tag = ENDIF_TAG + " " + var_name;
        std::string closing_else_tag = ELSE_TAG + " " + var_name;

        if(var_value.empty())
        {
            // search for the first <!--#endif var_name
            size_t endif_pos = templatized.find(closing_endif_tag);
            if(endif_pos != std::string::npos)
            {
                // search for the first <!--#else before the endif tag

                size_t else_pos = templatized.find(closing_else_tag);
                if(else_pos < endif_pos)
                {
                    // this is the else of this if, removing the content of the if till the else
                    templatized = templatized.substr(0, if_pos) + templatized.substr(else_pos + closing_else_tag.length() + 4);
                    // we still need to remove the endif
                    size_t final_endif_pos = templatized.find(closing_endif_tag);
                    if(final_endif_pos != std::string::npos)
                    {
                        templatized = templatized.substr(0, final_endif_pos) + templatized.substr(final_endif_pos + closing_endif_tag.length() + 4);
                    }

                }
                else
                {
                    // no else, just remove the if tag
                    templatized = templatized.substr(0, if_pos) + templatized.substr(endif_pos + closing_endif_tag.length() + 4);
                }
            }
            else
            {
                // oops, user forgot the enclosing endif, just return what is here
                return templatized;
            }
        }
        else // remove the comments :)
        {
            size_t endif_pos = templatized.find(closing_endif_tag);

            if(endif_pos != std::string::npos)
            {
                size_t remove_start_pos = endif_pos;
                std::string remove_tag = closing_endif_tag;
                size_t additional_remove = 0; // count ofchars between else and endif
                // search for the first <!--#else before the endif tag
                size_t else_pos = templatized.find(closing_else_tag);
                if(else_pos < endif_pos)
                {
                    remove_start_pos = else_pos;
                    additional_remove = endif_pos - remove_start_pos;
                }

                std::string before_if = templatized.substr(0, if_pos);
                std::string between = templatized.substr(i + 3, remove_start_pos - i - 3);
                std::string after_endif = templatized.substr(remove_start_pos + additional_remove + closing_endif_tag.length() + 4);

                templatized =  before_if + between + after_endif;
            }
            else
            {
                // oops, user forgot the enclosing endif, just return what is here
                return templatized;
            }
        }
    }

    return templatized;
}

std::string templater_base::resolve_ifeq(size_t if_pos, std::string templatized)
{
    size_t i = if_pos;

    // skip the <!--#ifeq
    i += IFEQ_TAG.length();

    // and any whitespace that might follow
    skip_whitespace(templatized, i);

    // now extract the variable
    if(i < templatized.length())
    {
        char c_sep = 32;
        std::string what_to = extract_identifier_word(templatized, i, {' ', '#'}, {',','-'}, std::move(c_sep));
        skip_whitespace(templatized, i);

        size_t comma_pos = what_to.find(',');
        bool are_eq = true;
        if(comma_pos == std::string::npos)
        {
            are_eq = false;
        }
        else
        {
            std::string before = what_to.substr(0, comma_pos);
            std::string after =  what_to.substr(comma_pos + 1);

            are_eq = before == after;
        }


        if(!are_eq)
        {
            // search for the first <!--#endifeq
            size_t endif_pos = templatized.find(ENDEQ_TAG);
            if(endif_pos != std::string::npos)
            {
                templatized = templatized.substr(0, if_pos) + templatized.substr(endif_pos + ENDEQ_TAG.length() + 4);
            }
        }
        else // remove the comments :)
        {
            size_t endif_pos = templatized.find(ENDEQ_TAG);

            if(endif_pos != std::string::npos)
            {
                size_t remove_start_pos = endif_pos;
                std::string before_if = templatized.substr(0, if_pos);
                std::string between = templatized.substr(i + 3, remove_start_pos - i - 3);
                std::string after_endif = templatized.substr(remove_start_pos + ENDEQ_TAG.length() + 4);

                templatized =  before_if + between + after_endif;
            }
        }
    }

    return templatized;
}

std::string templater_base::resolve_defines(std::string content)
{
    size_t define_pos = content.find(DEFINE_TAG);

    std::vector<std::pair<size_t, size_t>> strings_to_remove; // we will remove the define tags from in here
    if(define_pos != std::string::npos)
    {
        while(define_pos != std::string::npos)
        {
            bool define_read = false;
            // skipping the tag
            size_t i = define_pos + DEFINE_TAG.length();
            // skipping the whitespace after the tag
            skip_whitespace(content, i);
            while(!define_read)
            {

                std::string var_name = "";
                std::string var_value = "";
                // now comes a list of comma separated variable=value assignments or just simply variable
                while(i < content.length() && content[i] != '=' && content[i] != ',' && content[i] != '#'
                      && (isalnum(content[i]) || content[i] == '_' ))
                {
                    var_name += content[i];
                    i ++;
                }
                // now if content[i] == '=' we need to parse out the value from it
                if(content[i] == '=')
                {
                    i ++;
                    bool string_comes = false;
                    if(content[i] == '"') // string comes after this?
                    {
                        i++;
                        string_comes = true;
                    }
                    while( (i < content.length() && content[i] != ',' && content[i] != '#' && !string_comes)
                           || (i < content.length() && string_comes && content[i] != '"'))
                    {
                        if(content[i] != '\\') // escaping a quote in the string
                        {
                            var_value += content[i];
                        }
                        else
                        {
                            i++;
                            var_value += content[i];
                        }
                        i++;
                    }
                }
                else
                {
                    var_value = "true";
                }
                kps.insert({var_name, var_value});
                if(content[i] == '#')
                {
                    i++;
                    if(i + 2 < content.length() && content[i] == '-' && content[i+1] == '-' && content[i+2]=='>')
                    {
                        i += 3; // skip the closing comment
                        define_read = true;
                    }
                    else
                    {
                        // this is a malformed entry, just give up
                        return content;
                    }
                }
                else
                {
                    if(content[i] == ',')
                    {
                        i++;
                    }
                }
            }

            strings_to_remove.insert(strings_to_remove.begin(), {define_pos, i});
            define_pos = content.find(DEFINE_TAG, i);
        }
    }
    // now walk through the strings_to_remove and delete from the content everything from there
    for(const auto& beg_end_pos : strings_to_remove)
    {
        content.erase(beg_end_pos.first, beg_end_pos.second - beg_end_pos.first);
    }

    return content;
}

std::string templater_base::resolve_scripts(std::string templatized, const std::string& tag)
{
    bool done = false;
    while(!done)
    {
        size_t script_tag_pos = templatized.find(tag);
        if(script_tag_pos != std::string::npos)
        {
            while(script_tag_pos != std::string::npos)
            {
                templatized = resolve_script(script_tag_pos, templatized, tag);
                script_tag_pos = templatized.find(tag);
            }
        }
        else
        {
            done = true;
        }
    }
    return templatized;
}

std::string templater_base::resolve_script(size_t pos, const std::string& content, const std::string &tag)
{
    size_t endscript_start_pos = content.find(ENDSCRIPT_TAG);
    size_t old_pos = pos;

    // skip the tag
    pos += tag.length();
    // skip the space(s) followwing the "script"
    while(isspace(content[pos])) pos += 1;

    std::string script_type = "";
    while(pos < content.length() && content[pos] != '#' && content[pos] != ':')
    {
        script_type += content[pos++];
    }

    if(pos == content.length())
    {
        set_error("Script tag at", old_pos, "is not closed");
        return content.substr(0, old_pos);
    }

    static const auto script_types = {"python"};

    bool found = false;
    for(const auto& st : script_types)
    {
        if(st == script_type) found = true;
    }

    if(!found)
    {
        set_error("Unsupported scripting language", script_type);
        return content.substr(0, old_pos);
    }

    // pos points to the script closing tag
    std::string before_script = content.substr(0, old_pos);
    std::string between = std::string(content.begin() + pos + 4,  // #--> is the 4
                                      content.begin() + endscript_start_pos);
    std::string after_script = content.substr(endscript_start_pos + ENDSCRIPT_TAG.length() + 4);

    std::string result = python_runner().run(kps, between, variables(true));


    return before_script + unafrog::utils::trim(result) + after_script;
}

void stringholder::replace_all(const std::string &from, const std::string &to)
{
    if(from.empty())
    {
        return;
    }
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos)
    {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}

stringholder &stringholder::templatize(const std::map<std::string, std::string> &m)
{
    for(auto it = m.begin(); it != m.end(); ++ it)
    {
        replace_all(TEMPLATE_VARIABLE_START_DELIMITER + it->first + TEMPLATE_VARIABLE_END_DELIMITER, it->second );
    }
    return *this;
}

template_warehouse &template_warehouse::instance()
{
    static template_warehouse twh;
    return twh;
}

bool template_warehouse::checkTemplates()
{
    for(auto it = templates.begin(); it != templates.end(); ++it)
    {
        if(!it->second->check())
        {
            return false;
        }
    }
    debug() << "All templates OK";
    return true;
}

std::string template_warehouse::getTemplateContent(const std::string &templateName)
{
    if(templates.count(templateName))
    {
        std::shared_ptr<template_base> tb = templates[templateName];
        std::string c = tb->content();
        return c;
    }
    else
    {
        return "Not found:" + templateName;
    }
}

bool template_warehouse::registerTemplate(const std::string &name, template_base *templateClass)
{
    // although this code seems stupid, it is due to the fact that every inclusion of the
    // templater.h will cause a new object to be created, but we want only one
    if(templates.count(name))
    {
        debug() << "Freeing existing template class:" << name;
        delete templateClass;
        return false;
    }
    debug() << "Registering template class:" << name;
    templates[name] = std::shared_ptr<template_base>(templateClass);
    return true;
}


std::string file_template::base_dir() const
{
    namespace fs = std::filesystem;
    if(fs::is_regular_file(fileName()))
    {
        return "";
    }
    else
    {
        return TEMPLATES_DIRECTORY;
    }

}

std::string file_template::content() const
{
    std::string fn = base_dir() + fileName();
    std::ifstream ifs(fn);
    std::string s( (std::istreambuf_iterator<char>(ifs) ),
                   (std::istreambuf_iterator<char>()) );
    return s;
}

bool file_template::check() const
{
    struct stat buffer;
    bool res = (stat ( (base_dir() +  fileName()).c_str(), &buffer) == 0);
    if(!res)
    {
        debug() << "Check for "<< fileName() << " failed";
    }
    return res;
}