mirror of
https://github.com/pantor/inja.git
synced 2026-03-03 15:56:25 +00:00
split dev files and amalgamate into a single header
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -38,3 +38,5 @@ dist
|
||||
|
||||
# Coveralls repo token
|
||||
.coveralls.yml
|
||||
|
||||
.vscode
|
||||
|
||||
@@ -32,7 +32,8 @@ endif()
|
||||
##
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
set(INJA_SOURCE_DIR src)
|
||||
set(INJA_INCLUDE_DIR include)
|
||||
set(INJA_SINGLE_INCLUDE_DIR single_include)
|
||||
set(INJA_HEADER_INSTALL_DIR include)
|
||||
|
||||
if(WIN32 AND MSVC AND MSVC_VERSION LESS 1900)
|
||||
@@ -50,26 +51,39 @@ if(BUILD_UNIT_TESTS)
|
||||
endif()
|
||||
|
||||
|
||||
##
|
||||
## AMALGAMATE
|
||||
## amalgamate header files into single_include
|
||||
##
|
||||
execute_process(COMMAND python3 amalgamate/amalgamate.py -c amalgamate/config.json -s include
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
|
||||
|
||||
##
|
||||
## TARGETS
|
||||
## Build targets for the interface library
|
||||
##
|
||||
add_library(inja INTERFACE)
|
||||
target_include_directories(inja INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INJA_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INJA_INCLUDE_DIR}>
|
||||
$<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
|
||||
)
|
||||
|
||||
|
||||
add_library(inja_single INTERFACE)
|
||||
target_include_directories(inja_single INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INJA_SINGLE_INCLUDE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INJA_INCLUDE_DIR}>
|
||||
$<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
|
||||
)
|
||||
|
||||
|
||||
if(HUNTER_ENABLED) # Use Hunter to manage dependencies
|
||||
# Add JSON package
|
||||
hunter_add_package(nlohmann_json)
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
# Add dependencies to target
|
||||
target_link_libraries(inja INTERFACE nlohmann_json)
|
||||
else()
|
||||
target_include_directories(inja INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/test/thirdparty>
|
||||
$<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -106,7 +120,7 @@ install(
|
||||
)
|
||||
|
||||
install(
|
||||
FILES ${INJA_SOURCE_DIR}/inja.hpp
|
||||
FILES ${INJA_INCLUDE_DIR}/inja.hpp
|
||||
DESTINATION "${include_install_dir}"
|
||||
)
|
||||
|
||||
|
||||
27
amalgamate/LICENSE.md
Normal file
27
amalgamate/LICENSE.md
Normal file
@@ -0,0 +1,27 @@
|
||||
amalgamate.py - Amalgamate C source and header files
|
||||
Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Erik Edlund, nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
294
amalgamate/amalgamate.py
Executable file
294
amalgamate/amalgamate.py
Executable file
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# amalgamate.py - Amalgamate C source and header files.
|
||||
# Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of Erik Edlund, nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
class Amalgamation(object):
|
||||
|
||||
# Prepends self.source_path to file_path if needed.
|
||||
def actual_path(self, file_path):
|
||||
if not os.path.isabs(file_path):
|
||||
file_path = os.path.join(self.source_path, file_path)
|
||||
return file_path
|
||||
|
||||
# Search included file_path in self.include_paths and
|
||||
# in source_dir if specified.
|
||||
def find_included_file(self, file_path, source_dir):
|
||||
search_dirs = self.include_paths[:]
|
||||
if source_dir:
|
||||
search_dirs.insert(0, source_dir)
|
||||
|
||||
for search_dir in search_dirs:
|
||||
search_path = os.path.join(search_dir, file_path)
|
||||
if os.path.isfile(self.actual_path(search_path)):
|
||||
return search_path
|
||||
return None
|
||||
|
||||
def __init__(self, args):
|
||||
with open(args.config, 'r') as f:
|
||||
config = json.loads(f.read())
|
||||
for key in config:
|
||||
setattr(self, key, config[key])
|
||||
|
||||
self.verbose = args.verbose == "yes"
|
||||
self.prologue = args.prologue
|
||||
self.source_path = args.source_path
|
||||
self.included_files = []
|
||||
|
||||
# Generate the amalgamation and write it to the target file.
|
||||
def generate(self):
|
||||
amalgamation = ""
|
||||
|
||||
if self.prologue:
|
||||
with open(self.prologue, 'r') as f:
|
||||
amalgamation += datetime.datetime.now().strftime(f.read())
|
||||
|
||||
if self.verbose:
|
||||
print("Config:")
|
||||
print(" target = {0}".format(self.target))
|
||||
print(" working_dir = {0}".format(os.getcwd()))
|
||||
print(" include_paths = {0}".format(self.include_paths))
|
||||
print("Creating amalgamation:")
|
||||
for file_path in self.sources:
|
||||
# Do not check the include paths while processing the source
|
||||
# list, all given source paths must be correct.
|
||||
actual_path = self.actual_path(file_path)
|
||||
print(" - processing \"{0}\"".format(file_path))
|
||||
t = TranslationUnit(file_path, self, True)
|
||||
amalgamation += t.content
|
||||
|
||||
with open(self.target, 'w') as f:
|
||||
f.write(amalgamation)
|
||||
|
||||
print("...done!\n")
|
||||
if self.verbose:
|
||||
print("Files processed: {0}".format(self.sources))
|
||||
print("Files included: {0}".format(self.included_files))
|
||||
print("")
|
||||
|
||||
class TranslationUnit(object):
|
||||
|
||||
# // C++ comment.
|
||||
cpp_comment_pattern = re.compile(r"//.*?\n")
|
||||
|
||||
# /* C comment. */
|
||||
c_comment_pattern = re.compile(r"/\*.*?\*/", re.S)
|
||||
|
||||
# "complex \"stri\\\ng\" value".
|
||||
string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S)
|
||||
|
||||
# Handle simple include directives. Support for advanced
|
||||
# directives where macros and defines needs to expanded is
|
||||
# not a concern right now.
|
||||
include_pattern = re.compile(
|
||||
r'#\s*include\s+(<|")(?P<path>.*?)("|>)', re.S)
|
||||
|
||||
# #pragma once
|
||||
pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S)
|
||||
|
||||
# Search for pattern in self.content, add the match to
|
||||
# contexts if found and update the index accordingly.
|
||||
def _search_content(self, index, pattern, contexts):
|
||||
match = pattern.search(self.content, index)
|
||||
if match:
|
||||
contexts.append(match)
|
||||
return match.end()
|
||||
return index + 2
|
||||
|
||||
# Return all the skippable contexts, i.e., comments and strings
|
||||
def _find_skippable_contexts(self):
|
||||
# Find contexts in the content in which a found include
|
||||
# directive should not be processed.
|
||||
skippable_contexts = []
|
||||
|
||||
# Walk through the content char by char, and try to grab
|
||||
# skippable contexts using regular expressions when found.
|
||||
i = 1
|
||||
content_len = len(self.content)
|
||||
while i < content_len:
|
||||
j = i - 1
|
||||
current = self.content[i]
|
||||
previous = self.content[j]
|
||||
|
||||
if current == '"':
|
||||
# String value.
|
||||
i = self._search_content(j, self.string_pattern,
|
||||
skippable_contexts)
|
||||
elif current == '*' and previous == '/':
|
||||
# C style comment.
|
||||
i = self._search_content(j, self.c_comment_pattern,
|
||||
skippable_contexts)
|
||||
elif current == '/' and previous == '/':
|
||||
# C++ style comment.
|
||||
i = self._search_content(j, self.cpp_comment_pattern,
|
||||
skippable_contexts)
|
||||
else:
|
||||
# Skip to the next char.
|
||||
i += 1
|
||||
|
||||
return skippable_contexts
|
||||
|
||||
# Returns True if the match is within list of other matches
|
||||
def _is_within(self, match, matches):
|
||||
for m in matches:
|
||||
if match.start() > m.start() and \
|
||||
match.end() < m.end():
|
||||
return True
|
||||
return False
|
||||
|
||||
# Removes pragma once from content
|
||||
def _process_pragma_once(self):
|
||||
content_len = len(self.content)
|
||||
if content_len < len("#include <x>"):
|
||||
return 0
|
||||
|
||||
# Find contexts in the content in which a found include
|
||||
# directive should not be processed.
|
||||
skippable_contexts = self._find_skippable_contexts()
|
||||
|
||||
pragmas = []
|
||||
pragma_once_match = self.pragma_once_pattern.search(self.content)
|
||||
while pragma_once_match:
|
||||
if not self._is_within(pragma_once_match, skippable_contexts):
|
||||
pragmas.append(pragma_once_match)
|
||||
|
||||
pragma_once_match = self.pragma_once_pattern.search(self.content,
|
||||
pragma_once_match.end())
|
||||
|
||||
# Handle all collected pragma once directives.
|
||||
prev_end = 0
|
||||
tmp_content = ''
|
||||
for pragma_match in pragmas:
|
||||
tmp_content += self.content[prev_end:pragma_match.start()]
|
||||
prev_end = pragma_match.end()
|
||||
tmp_content += self.content[prev_end:]
|
||||
self.content = tmp_content
|
||||
|
||||
# Include all trivial #include directives into self.content.
|
||||
def _process_includes(self):
|
||||
content_len = len(self.content)
|
||||
if content_len < len("#include <x>"):
|
||||
return 0
|
||||
|
||||
# Find contexts in the content in which a found include
|
||||
# directive should not be processed.
|
||||
skippable_contexts = self._find_skippable_contexts()
|
||||
|
||||
# Search for include directives in the content, collect those
|
||||
# which should be included into the content.
|
||||
includes = []
|
||||
include_match = self.include_pattern.search(self.content)
|
||||
while include_match:
|
||||
if not self._is_within(include_match, skippable_contexts):
|
||||
include_path = include_match.group("path")
|
||||
search_same_dir = include_match.group(1) == '"'
|
||||
found_included_path = self.amalgamation.find_included_file(
|
||||
include_path, self.file_dir if search_same_dir else None)
|
||||
if found_included_path:
|
||||
includes.append((include_match, found_included_path))
|
||||
|
||||
include_match = self.include_pattern.search(self.content,
|
||||
include_match.end())
|
||||
|
||||
# Handle all collected include directives.
|
||||
prev_end = 0
|
||||
tmp_content = ''
|
||||
for include in includes:
|
||||
include_match, found_included_path = include
|
||||
tmp_content += self.content[prev_end:include_match.start()]
|
||||
tmp_content += "// {0}\n".format(include_match.group(0))
|
||||
if not found_included_path in self.amalgamation.included_files:
|
||||
t = TranslationUnit(found_included_path, self.amalgamation, False)
|
||||
tmp_content += t.content
|
||||
prev_end = include_match.end()
|
||||
tmp_content += self.content[prev_end:]
|
||||
self.content = tmp_content
|
||||
|
||||
return len(includes)
|
||||
|
||||
# Make all content processing
|
||||
def _process(self):
|
||||
if not self.is_root:
|
||||
self._process_pragma_once()
|
||||
self._process_includes()
|
||||
|
||||
def __init__(self, file_path, amalgamation, is_root):
|
||||
self.file_path = file_path
|
||||
self.file_dir = os.path.dirname(file_path)
|
||||
self.amalgamation = amalgamation
|
||||
self.is_root = is_root
|
||||
|
||||
self.amalgamation.included_files.append(self.file_path)
|
||||
|
||||
actual_path = self.amalgamation.actual_path(file_path)
|
||||
if not os.path.isfile(actual_path):
|
||||
raise IOError("File not found: \"{0}\"".format(file_path))
|
||||
with open(actual_path, 'r') as f:
|
||||
self.content = f.read()
|
||||
self._process()
|
||||
|
||||
def main():
|
||||
description = "Amalgamate C source and header files."
|
||||
usage = " ".join([
|
||||
"amalgamate.py",
|
||||
"[-v]",
|
||||
"-c path/to/config.json",
|
||||
"-s path/to/source/dir",
|
||||
"[-p path/to/prologue.(c|h)]"
|
||||
])
|
||||
argsparser = argparse.ArgumentParser(
|
||||
description=description, usage=usage)
|
||||
|
||||
argsparser.add_argument("-v", "--verbose", dest="verbose",
|
||||
choices=["yes", "no"], metavar="", help="be verbose")
|
||||
|
||||
argsparser.add_argument("-c", "--config", dest="config",
|
||||
required=True, metavar="", help="path to a JSON config file")
|
||||
|
||||
argsparser.add_argument("-s", "--source", dest="source_path",
|
||||
required=True, metavar="", help="source code path")
|
||||
|
||||
argsparser.add_argument("-p", "--prologue", dest="prologue",
|
||||
required=False, metavar="", help="path to a C prologue file")
|
||||
|
||||
amalgamation = Amalgamation(argsparser.parse_args())
|
||||
amalgamation.generate()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
amalgamate/config.json
Normal file
9
amalgamate/config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"project": "inja",
|
||||
"target": "single_include/inja.hpp",
|
||||
"sources": [
|
||||
"../include/inja.hpp"
|
||||
],
|
||||
"include_paths": [
|
||||
]
|
||||
}
|
||||
130
include/environment.hpp
Normal file
130
include/environment.hpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#ifndef PANTOR_INJA_ENVIRONMENT_HPP
|
||||
#define PANTOR_INJA_ENVIRONMENT_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <regex.hpp>
|
||||
#include <parser.hpp>
|
||||
#include <renderer.hpp>
|
||||
#include <template.hpp>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
/*!
|
||||
@brief Environment class
|
||||
*/
|
||||
class Environment {
|
||||
const std::string input_path;
|
||||
const std::string output_path;
|
||||
|
||||
Parser parser;
|
||||
Renderer renderer;
|
||||
|
||||
public:
|
||||
Environment(): Environment("./") { }
|
||||
explicit Environment(const std::string& global_path): input_path(global_path), output_path(global_path), parser() { }
|
||||
explicit Environment(const std::string& input_path, const std::string& output_path): input_path(input_path), output_path(output_path), parser() { }
|
||||
|
||||
void set_statement(const std::string& open, const std::string& close) {
|
||||
parser.regex_map_delimiters[Parsed::Delimiter::Statement] = Regex{open + "\\s*(.+?)\\s*" + close};
|
||||
}
|
||||
|
||||
void set_line_statement(const std::string& open) {
|
||||
parser.regex_map_delimiters[Parsed::Delimiter::LineStatement] = Regex{"(?:^|\\n)" + open + " *(.+?) *(?:\\n|$)"};
|
||||
}
|
||||
|
||||
void set_expression(const std::string& open, const std::string& close) {
|
||||
parser.regex_map_delimiters[Parsed::Delimiter::Expression] = Regex{open + "\\s*(.+?)\\s*" + close};
|
||||
}
|
||||
|
||||
void set_comment(const std::string& open, const std::string& close) {
|
||||
parser.regex_map_delimiters[Parsed::Delimiter::Comment] = Regex{open + "\\s*(.+?)\\s*" + close};
|
||||
}
|
||||
|
||||
void set_element_notation(const ElementNotation element_notation_) {
|
||||
parser.element_notation = element_notation_;
|
||||
}
|
||||
|
||||
Template parse(const std::string& input) {
|
||||
return parser.parse(input);
|
||||
}
|
||||
|
||||
Template parse_template(const std::string& filename) {
|
||||
return parser.parse_template(input_path + filename);
|
||||
}
|
||||
|
||||
std::string render(const std::string& input, const json& data) {
|
||||
return renderer.render(parse(input), data);
|
||||
}
|
||||
|
||||
std::string render_template(const Template& temp, const json& data) {
|
||||
return renderer.render(temp, data);
|
||||
}
|
||||
|
||||
std::string render_file(const std::string& filename, const json& data) {
|
||||
return renderer.render(parse_template(filename), data);
|
||||
}
|
||||
|
||||
std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) {
|
||||
const json data = load_json(filename_data);
|
||||
return render_file(filename, data);
|
||||
}
|
||||
|
||||
void write(const std::string& filename, const json& data, const std::string& filename_out) {
|
||||
std::ofstream file(output_path + filename_out);
|
||||
file << render_file(filename, data);
|
||||
file.close();
|
||||
}
|
||||
|
||||
void write(const Template& temp, const json& data, const std::string& filename_out) {
|
||||
std::ofstream file(output_path + filename_out);
|
||||
file << render_template(temp, data);
|
||||
file.close();
|
||||
}
|
||||
|
||||
void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) {
|
||||
const json data = load_json(filename_data);
|
||||
write(filename, data, filename_out);
|
||||
}
|
||||
|
||||
void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) {
|
||||
const json data = load_json(filename_data);
|
||||
write(temp, data, filename_out);
|
||||
}
|
||||
|
||||
std::string load_global_file(const std::string& filename) {
|
||||
return parser.load_file(input_path + filename);
|
||||
}
|
||||
|
||||
json load_json(const std::string& filename) {
|
||||
std::ifstream file(input_path + filename);
|
||||
json j;
|
||||
file >> j;
|
||||
return j;
|
||||
}
|
||||
|
||||
void add_callback(std::string name, int number_arguments, const std::function<json(const Parsed::Arguments&, const json&)>& callback) {
|
||||
const Parsed::CallbackSignature signature = std::make_pair(name, number_arguments);
|
||||
parser.regex_map_callbacks[signature] = Parser::function_regex(name, number_arguments);
|
||||
renderer.map_callbacks[signature] = callback;
|
||||
}
|
||||
|
||||
void include_template(std::string name, const Template& temp) {
|
||||
parser.included_templates[name] = temp;
|
||||
}
|
||||
|
||||
template<typename T = json>
|
||||
T get_argument(const Parsed::Arguments& args, int index, const json& data) {
|
||||
return renderer.eval_expression<T>(args[index], data);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_ENVIRONMENT_HPP
|
||||
18
include/error.hpp
Normal file
18
include/error.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef PANTOR_INJA_ERROR_HPP
|
||||
#define PANTOR_INJA_ERROR_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
/*!
|
||||
@brief throw an error with a given message
|
||||
*/
|
||||
inline void inja_throw(const std::string& type, const std::string& message) {
|
||||
throw std::runtime_error("[inja.exception." + type + "] " + message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_ERROR_HPP
|
||||
49
include/inja.hpp
Normal file
49
include/inja.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Inja - A Template Engine for Modern C++
|
||||
version 1.1.0
|
||||
https://github.com/pantor/inja
|
||||
|
||||
Licensed under the MIT License <https://opensource.org/licenses/MIT>.
|
||||
Copyright (c) 2017-2018 Pantor <https://github.com/pantor>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PANTOR_INJA_HPP
|
||||
#define PANTOR_INJA_HPP
|
||||
|
||||
#define PANTOR_INJA_VERSION_MAJOR 1
|
||||
#define PANTOR_INJA_VERSION_MINOR 1
|
||||
#define PANTOR_INJA_VERSION_PATCH 0
|
||||
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
||||
#include "error.hpp"
|
||||
#include "regex.hpp"
|
||||
#include "parsed.hpp"
|
||||
#include "template.hpp"
|
||||
#include "renderer.hpp"
|
||||
#include "parser.hpp"
|
||||
#include "environment.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
|
||||
#endif // PANTOR_INJA_HPP
|
||||
154
include/parsed.hpp
Normal file
154
include/parsed.hpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#ifndef PANTOR_INJA_PARSED_HPP
|
||||
#define PANTOR_INJA_PARSED_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
enum class ElementNotation {
|
||||
Dot,
|
||||
Pointer
|
||||
};
|
||||
|
||||
struct Parsed {
|
||||
enum class Type {
|
||||
Comment,
|
||||
Condition,
|
||||
ConditionBranch,
|
||||
Expression,
|
||||
Loop,
|
||||
Main,
|
||||
String
|
||||
};
|
||||
|
||||
enum class Delimiter {
|
||||
Comment,
|
||||
Expression,
|
||||
LineStatement,
|
||||
Statement
|
||||
};
|
||||
|
||||
enum class Statement {
|
||||
Condition,
|
||||
Include,
|
||||
Loop
|
||||
};
|
||||
|
||||
enum class Function {
|
||||
Not,
|
||||
And,
|
||||
Or,
|
||||
In,
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
Different,
|
||||
Callback,
|
||||
DivisibleBy,
|
||||
Even,
|
||||
First,
|
||||
Float,
|
||||
Int,
|
||||
Last,
|
||||
Length,
|
||||
Lower,
|
||||
Max,
|
||||
Min,
|
||||
Odd,
|
||||
Range,
|
||||
Result,
|
||||
Round,
|
||||
Sort,
|
||||
Upper,
|
||||
ReadJson,
|
||||
Exists,
|
||||
ExistsInObject,
|
||||
IsBoolean,
|
||||
IsNumber,
|
||||
IsInteger,
|
||||
IsFloat,
|
||||
IsObject,
|
||||
IsArray,
|
||||
IsString,
|
||||
Default
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
If,
|
||||
ElseIf,
|
||||
Else
|
||||
};
|
||||
|
||||
enum class Loop {
|
||||
ForListIn,
|
||||
ForMapIn
|
||||
};
|
||||
|
||||
struct Element {
|
||||
Type type;
|
||||
std::string inner;
|
||||
std::vector<std::shared_ptr<Element>> children;
|
||||
|
||||
explicit Element(): Element(Type::Main, "") { }
|
||||
explicit Element(const Type type): Element(type, "") { }
|
||||
explicit Element(const Type type, const std::string& inner): type(type), inner(inner), children({}) { }
|
||||
};
|
||||
|
||||
struct ElementString: public Element {
|
||||
const std::string text;
|
||||
|
||||
explicit ElementString(const std::string& text): Element(Type::String), text(text) { }
|
||||
};
|
||||
|
||||
struct ElementComment: public Element {
|
||||
const std::string text;
|
||||
|
||||
explicit ElementComment(const std::string& text): Element(Type::Comment), text(text) { }
|
||||
};
|
||||
|
||||
struct ElementExpression: public Element {
|
||||
Function function;
|
||||
std::vector<ElementExpression> args;
|
||||
std::string command;
|
||||
json result;
|
||||
|
||||
explicit ElementExpression(): ElementExpression(Function::ReadJson) { }
|
||||
explicit ElementExpression(const Function function_): Element(Type::Expression), function(function_), args({}), command("") { }
|
||||
};
|
||||
|
||||
struct ElementLoop: public Element {
|
||||
Loop loop;
|
||||
const std::string key;
|
||||
const std::string value;
|
||||
const ElementExpression list;
|
||||
|
||||
explicit ElementLoop(const Loop loop_, const std::string& value, const ElementExpression& list, const std::string& inner): Element(Type::Loop, inner), loop(loop_), value(value), list(list) { }
|
||||
explicit ElementLoop(const Loop loop_, const std::string& key, const std::string& value, const ElementExpression& list, const std::string& inner): Element(Type::Loop, inner), loop(loop_), key(key), value(value), list(list) { }
|
||||
};
|
||||
|
||||
struct ElementConditionContainer: public Element {
|
||||
explicit ElementConditionContainer(): Element(Type::Condition) { }
|
||||
};
|
||||
|
||||
struct ElementConditionBranch: public Element {
|
||||
const Condition condition_type;
|
||||
const ElementExpression condition;
|
||||
|
||||
explicit ElementConditionBranch(const std::string& inner, const Condition condition_type): Element(Type::ConditionBranch, inner), condition_type(condition_type) { }
|
||||
explicit ElementConditionBranch(const std::string& inner, const Condition condition_type, const ElementExpression& condition): Element(Type::ConditionBranch, inner), condition_type(condition_type), condition(condition) { }
|
||||
};
|
||||
|
||||
using Arguments = std::vector<ElementExpression>;
|
||||
using CallbackSignature = std::pair<std::string, size_t>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_PARSED_HPP
|
||||
345
include/parser.hpp
Normal file
345
include/parser.hpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#ifndef PANTOR_INJA_PARSER_HPP
|
||||
#define PANTOR_INJA_PARSER_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <regex.hpp>
|
||||
#include <template.hpp>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
ElementNotation element_notation = ElementNotation::Pointer;
|
||||
|
||||
std::map<Parsed::CallbackSignature, Regex, std::greater<Parsed::CallbackSignature>> regex_map_callbacks;
|
||||
|
||||
std::map<const std::string, Template> included_templates;
|
||||
|
||||
/*!
|
||||
@brief create a corresponding regex for a function name with a number of arguments separated by ,
|
||||
*/
|
||||
static Regex function_regex(const std::string& name, int number_arguments) {
|
||||
std::string pattern = name;
|
||||
pattern.append("(?:\\(");
|
||||
for (int i = 0; i < number_arguments; i++) {
|
||||
if (i != 0) pattern.append(",");
|
||||
pattern.append("(.*)");
|
||||
}
|
||||
pattern.append("\\))");
|
||||
if (number_arguments == 0) { // Without arguments, allow to use the callback without parenthesis
|
||||
pattern.append("?");
|
||||
}
|
||||
return Regex{"\\s*" + pattern + "\\s*"};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief dot notation to json pointer notation
|
||||
*/
|
||||
static std::string dot_to_json_pointer_notation(const std::string& dot) {
|
||||
std::string result = dot;
|
||||
while (result.find(".") != std::string::npos) {
|
||||
result.replace(result.find("."), 1, "/");
|
||||
}
|
||||
result.insert(0, "/");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<Parsed::Delimiter, Regex> regex_map_delimiters = {
|
||||
{Parsed::Delimiter::Statement, Regex{"\\{\\%\\s*(.+?)\\s*\\%\\}"}},
|
||||
{Parsed::Delimiter::LineStatement, Regex{"(?:^|\\n)## *(.+?) *(?:\\n|$)"}},
|
||||
{Parsed::Delimiter::Expression, Regex{"\\{\\{\\s*(.+?)\\s*\\}\\}"}},
|
||||
{Parsed::Delimiter::Comment, Regex{"\\{#\\s*(.*?)\\s*#\\}"}}
|
||||
};
|
||||
|
||||
const std::map<Parsed::Statement, Regex> regex_map_statement_openers = {
|
||||
{Parsed::Statement::Loop, Regex{"for\\s+(.+)"}},
|
||||
{Parsed::Statement::Condition, Regex{"if\\s+(.+)"}},
|
||||
{Parsed::Statement::Include, Regex{"include\\s+\"(.+)\""}}
|
||||
};
|
||||
|
||||
const std::map<Parsed::Statement, Regex> regex_map_statement_closers = {
|
||||
{Parsed::Statement::Loop, Regex{"endfor"}},
|
||||
{Parsed::Statement::Condition, Regex{"endif"}}
|
||||
};
|
||||
|
||||
const std::map<Parsed::Loop, Regex> regex_map_loop = {
|
||||
{Parsed::Loop::ForListIn, Regex{"for\\s+(\\w+)\\s+in\\s+(.+)"}},
|
||||
{Parsed::Loop::ForMapIn, Regex{"for\\s+(\\w+),\\s+(\\w+)\\s+in\\s+(.+)"}},
|
||||
};
|
||||
|
||||
const std::map<Parsed::Condition, Regex> regex_map_condition = {
|
||||
{Parsed::Condition::If, Regex{"if\\s+(.+)"}},
|
||||
{Parsed::Condition::ElseIf, Regex{"else\\s+if\\s+(.+)"}},
|
||||
{Parsed::Condition::Else, Regex{"else"}}
|
||||
};
|
||||
|
||||
const std::map<Parsed::Function, Regex> regex_map_functions = {
|
||||
{Parsed::Function::Not, Regex{"not (.+)"}},
|
||||
{Parsed::Function::And, Regex{"(.+) and (.+)"}},
|
||||
{Parsed::Function::Or, Regex{"(.+) or (.+)"}},
|
||||
{Parsed::Function::In, Regex{"(.+) in (.+)"}},
|
||||
{Parsed::Function::Equal, Regex{"(.+) == (.+)"}},
|
||||
{Parsed::Function::Greater, Regex{"(.+) > (.+)"}},
|
||||
{Parsed::Function::Less, Regex{"(.+) < (.+)"}},
|
||||
{Parsed::Function::GreaterEqual, Regex{"(.+) >= (.+)"}},
|
||||
{Parsed::Function::LessEqual, Regex{"(.+) <= (.+)"}},
|
||||
{Parsed::Function::Different, Regex{"(.+) != (.+)"}},
|
||||
{Parsed::Function::Default, function_regex("default", 2)},
|
||||
{Parsed::Function::DivisibleBy, function_regex("divisibleBy", 2)},
|
||||
{Parsed::Function::Even, function_regex("even", 1)},
|
||||
{Parsed::Function::First, function_regex("first", 1)},
|
||||
{Parsed::Function::Float, function_regex("float", 1)},
|
||||
{Parsed::Function::Int, function_regex("int", 1)},
|
||||
{Parsed::Function::Last, function_regex("last", 1)},
|
||||
{Parsed::Function::Length, function_regex("length", 1)},
|
||||
{Parsed::Function::Lower, function_regex("lower", 1)},
|
||||
{Parsed::Function::Max, function_regex("max", 1)},
|
||||
{Parsed::Function::Min, function_regex("min", 1)},
|
||||
{Parsed::Function::Odd, function_regex("odd", 1)},
|
||||
{Parsed::Function::Range, function_regex("range", 1)},
|
||||
{Parsed::Function::Round, function_regex("round", 2)},
|
||||
{Parsed::Function::Sort, function_regex("sort", 1)},
|
||||
{Parsed::Function::Upper, function_regex("upper", 1)},
|
||||
{Parsed::Function::Exists, function_regex("exists", 1)},
|
||||
{Parsed::Function::ExistsInObject, function_regex("existsIn", 2)},
|
||||
{Parsed::Function::IsBoolean, function_regex("isBoolean", 1)},
|
||||
{Parsed::Function::IsNumber, function_regex("isNumber", 1)},
|
||||
{Parsed::Function::IsInteger, function_regex("isInteger", 1)},
|
||||
{Parsed::Function::IsFloat, function_regex("isFloat", 1)},
|
||||
{Parsed::Function::IsObject, function_regex("isObject", 1)},
|
||||
{Parsed::Function::IsArray, function_regex("isArray", 1)},
|
||||
{Parsed::Function::IsString, function_regex("isString", 1)},
|
||||
{Parsed::Function::ReadJson, Regex{"\\s*([^\\(\\)]*\\S)\\s*"}}
|
||||
};
|
||||
|
||||
Parser() { }
|
||||
|
||||
Parsed::ElementExpression parse_expression(const std::string& input) {
|
||||
const MatchType<Parsed::CallbackSignature> match_callback = match(input, regex_map_callbacks);
|
||||
if (!match_callback.type().first.empty()) {
|
||||
std::vector<Parsed::ElementExpression> args {};
|
||||
for (unsigned int i = 1; i < match_callback.size(); i++) { // str(0) is whole group
|
||||
args.push_back( parse_expression(match_callback.str(i)) );
|
||||
}
|
||||
|
||||
Parsed::ElementExpression result = Parsed::ElementExpression(Parsed::Function::Callback);
|
||||
result.args = args;
|
||||
result.command = match_callback.type().first;
|
||||
return result;
|
||||
}
|
||||
|
||||
const MatchType<Parsed::Function> match_function = match(input, regex_map_functions);
|
||||
switch ( match_function.type() ) {
|
||||
case Parsed::Function::ReadJson: {
|
||||
std::string command = match_function.str(1);
|
||||
if ( json::accept(command) ) { // JSON Result
|
||||
Parsed::ElementExpression result = Parsed::ElementExpression(Parsed::Function::Result);
|
||||
result.result = json::parse(command);
|
||||
return result;
|
||||
}
|
||||
|
||||
Parsed::ElementExpression result = Parsed::ElementExpression(Parsed::Function::ReadJson);
|
||||
switch (element_notation) {
|
||||
case ElementNotation::Pointer: {
|
||||
if (command[0] != '/') { command.insert(0, "/"); }
|
||||
result.command = command;
|
||||
break;
|
||||
}
|
||||
case ElementNotation::Dot: {
|
||||
result.command = dot_to_json_pointer_notation(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
default: {
|
||||
std::vector<Parsed::ElementExpression> args = {};
|
||||
for (unsigned int i = 1; i < match_function.size(); i++) { // str(0) is whole group
|
||||
args.push_back( parse_expression(match_function.str(i)) );
|
||||
}
|
||||
|
||||
Parsed::ElementExpression result = Parsed::ElementExpression(match_function.type());
|
||||
result.args = args;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Parsed::Element>> parse_level(const std::string& input, const std::string& path) {
|
||||
std::vector<std::shared_ptr<Parsed::Element>> result;
|
||||
|
||||
size_t current_position = 0;
|
||||
MatchType<Parsed::Delimiter> match_delimiter = search(input, regex_map_delimiters, current_position);
|
||||
while (match_delimiter.found()) {
|
||||
current_position = match_delimiter.end_position();
|
||||
const std::string string_prefix = match_delimiter.prefix();
|
||||
if (not string_prefix.empty()) {
|
||||
result.emplace_back( std::make_shared<Parsed::ElementString>(string_prefix) );
|
||||
}
|
||||
|
||||
const std::string delimiter_inner = match_delimiter.str(1);
|
||||
|
||||
switch ( match_delimiter.type() ) {
|
||||
case Parsed::Delimiter::Statement:
|
||||
case Parsed::Delimiter::LineStatement: {
|
||||
|
||||
const MatchType<Parsed::Statement> match_statement = match(delimiter_inner, regex_map_statement_openers);
|
||||
switch ( match_statement.type() ) {
|
||||
case Parsed::Statement::Loop: {
|
||||
const MatchClosed loop_match = search_closed(input, match_delimiter.regex(), regex_map_statement_openers.at(Parsed::Statement::Loop), regex_map_statement_closers.at(Parsed::Statement::Loop), match_delimiter);
|
||||
|
||||
current_position = loop_match.end_position();
|
||||
|
||||
const std::string loop_inner = match_statement.str(0);
|
||||
const MatchType<Parsed::Loop> match_command = match(loop_inner, regex_map_loop);
|
||||
if (not match_command.found()) {
|
||||
inja_throw("parser_error", "unknown loop statement: " + loop_inner);
|
||||
}
|
||||
switch (match_command.type()) {
|
||||
case Parsed::Loop::ForListIn: {
|
||||
const std::string value_name = match_command.str(1);
|
||||
const std::string list_name = match_command.str(2);
|
||||
|
||||
result.emplace_back( std::make_shared<Parsed::ElementLoop>(match_command.type(), value_name, parse_expression(list_name), loop_match.inner()));
|
||||
break;
|
||||
}
|
||||
case Parsed::Loop::ForMapIn: {
|
||||
const std::string key_name = match_command.str(1);
|
||||
const std::string value_name = match_command.str(2);
|
||||
const std::string list_name = match_command.str(3);
|
||||
|
||||
result.emplace_back( std::make_shared<Parsed::ElementLoop>(match_command.type(), key_name, value_name, parse_expression(list_name), loop_match.inner()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Parsed::Statement::Condition: {
|
||||
auto condition_container = std::make_shared<Parsed::ElementConditionContainer>();
|
||||
|
||||
Match condition_match = match_delimiter;
|
||||
MatchClosed else_if_match = search_closed_on_level(input, match_delimiter.regex(), regex_map_statement_openers.at(Parsed::Statement::Condition), regex_map_statement_closers.at(Parsed::Statement::Condition), regex_map_condition.at(Parsed::Condition::ElseIf), condition_match);
|
||||
while (else_if_match.found()) {
|
||||
condition_match = else_if_match.close_match;
|
||||
|
||||
const std::string else_if_match_inner = else_if_match.open_match.str(1);
|
||||
const MatchType<Parsed::Condition> match_command = match(else_if_match_inner, regex_map_condition);
|
||||
if (not match_command.found()) {
|
||||
inja_throw("parser_error", "unknown if statement: " + else_if_match.open_match.str());
|
||||
}
|
||||
condition_container->children.push_back( std::make_shared<Parsed::ElementConditionBranch>(else_if_match.inner(), match_command.type(), parse_expression(match_command.str(1))) );
|
||||
|
||||
else_if_match = search_closed_on_level(input, match_delimiter.regex(), regex_map_statement_openers.at(Parsed::Statement::Condition), regex_map_statement_closers.at(Parsed::Statement::Condition), regex_map_condition.at(Parsed::Condition::ElseIf), condition_match);
|
||||
}
|
||||
|
||||
MatchClosed else_match = search_closed_on_level(input, match_delimiter.regex(), regex_map_statement_openers.at(Parsed::Statement::Condition), regex_map_statement_closers.at(Parsed::Statement::Condition), regex_map_condition.at(Parsed::Condition::Else), condition_match);
|
||||
if (else_match.found()) {
|
||||
condition_match = else_match.close_match;
|
||||
|
||||
const std::string else_match_inner = else_match.open_match.str(1);
|
||||
const MatchType<Parsed::Condition> match_command = match(else_match_inner, regex_map_condition);
|
||||
if (not match_command.found()) {
|
||||
inja_throw("parser_error", "unknown if statement: " + else_match.open_match.str());
|
||||
}
|
||||
condition_container->children.push_back( std::make_shared<Parsed::ElementConditionBranch>(else_match.inner(), match_command.type(), parse_expression(match_command.str(1))) );
|
||||
}
|
||||
|
||||
const MatchClosed last_if_match = search_closed(input, match_delimiter.regex(), regex_map_statement_openers.at(Parsed::Statement::Condition), regex_map_statement_closers.at(Parsed::Statement::Condition), condition_match);
|
||||
if (not last_if_match.found()) {
|
||||
inja_throw("parser_error", "misordered if statement");
|
||||
}
|
||||
|
||||
const std::string last_if_match_inner = last_if_match.open_match.str(1);
|
||||
const MatchType<Parsed::Condition> match_command = match(last_if_match_inner, regex_map_condition);
|
||||
if (not match_command.found()) {
|
||||
inja_throw("parser_error", "unknown if statement: " + last_if_match.open_match.str());
|
||||
}
|
||||
if (match_command.type() == Parsed::Condition::Else) {
|
||||
condition_container->children.push_back( std::make_shared<Parsed::ElementConditionBranch>(last_if_match.inner(), match_command.type()) );
|
||||
} else {
|
||||
condition_container->children.push_back( std::make_shared<Parsed::ElementConditionBranch>(last_if_match.inner(), match_command.type(), parse_expression(match_command.str(1))) );
|
||||
}
|
||||
|
||||
current_position = last_if_match.end_position();
|
||||
result.emplace_back(condition_container);
|
||||
break;
|
||||
}
|
||||
case Parsed::Statement::Include: {
|
||||
const std::string template_name = match_statement.str(1);
|
||||
Template included_template;
|
||||
if (included_templates.find( template_name ) != included_templates.end()) {
|
||||
included_template = included_templates[template_name];
|
||||
} else {
|
||||
included_template = parse_template(path + template_name);
|
||||
}
|
||||
|
||||
auto children = included_template.parsed_template().children;
|
||||
result.insert(result.end(), children.begin(), children.end());
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Parsed::Delimiter::Expression: {
|
||||
result.emplace_back( std::make_shared<Parsed::ElementExpression>(parse_expression(delimiter_inner)) );
|
||||
break;
|
||||
}
|
||||
case Parsed::Delimiter::Comment: {
|
||||
result.emplace_back( std::make_shared<Parsed::ElementComment>(delimiter_inner) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match_delimiter = search(input, regex_map_delimiters, current_position);
|
||||
}
|
||||
if (current_position < input.length()) {
|
||||
result.emplace_back( std::make_shared<Parsed::ElementString>(input.substr(current_position)) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Parsed::Element> parse_tree(std::shared_ptr<Parsed::Element> current_element, const std::string& path) {
|
||||
if (not current_element->inner.empty()) {
|
||||
current_element->children = parse_level(current_element->inner, path);
|
||||
current_element->inner.clear();
|
||||
}
|
||||
|
||||
if (not current_element->children.empty()) {
|
||||
for (auto& child: current_element->children) {
|
||||
child = parse_tree(child, path);
|
||||
}
|
||||
}
|
||||
return current_element;
|
||||
}
|
||||
|
||||
Template parse(const std::string& input) {
|
||||
auto parsed = parse_tree(std::make_shared<Parsed::Element>(Parsed::Element(Parsed::Type::Main, input)), "./");
|
||||
return Template(*parsed);
|
||||
}
|
||||
|
||||
Template parse_template(const std::string& filename) {
|
||||
const std::string input = load_file(filename);
|
||||
const std::string path = filename.substr(0, filename.find_last_of("/\\") + 1);
|
||||
auto parsed = parse_tree(std::make_shared<Parsed::Element>(Parsed::Element(Parsed::Type::Main, input)), path);
|
||||
return Template(*parsed);
|
||||
}
|
||||
|
||||
std::string load_file(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_PARSER_HPP
|
||||
171
include/regex.hpp
Normal file
171
include/regex.hpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#ifndef PANTOR_INJA_REGEX_HPP
|
||||
#define PANTOR_INJA_REGEX_HPP
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
/*!
|
||||
@brief inja regex class, saves string pattern in addition to std::regex
|
||||
*/
|
||||
class Regex: public std::regex {
|
||||
std::string pattern_;
|
||||
|
||||
public:
|
||||
Regex(): std::regex() {}
|
||||
explicit Regex(const std::string& pattern): std::regex(pattern, std::regex_constants::ECMAScript), pattern_(pattern) { }
|
||||
|
||||
std::string pattern() const { return pattern_; }
|
||||
};
|
||||
|
||||
|
||||
class Match: public std::match_results<std::string::const_iterator> {
|
||||
size_t offset_ {0};
|
||||
unsigned int group_offset_ {0};
|
||||
Regex regex_;
|
||||
|
||||
public:
|
||||
Match(): std::match_results<std::string::const_iterator>() { }
|
||||
explicit Match(size_t offset): std::match_results<std::string::const_iterator>(), offset_(offset) { }
|
||||
explicit Match(size_t offset, const Regex& regex): std::match_results<std::string::const_iterator>(), offset_(offset), regex_(regex) { }
|
||||
|
||||
void set_group_offset(unsigned int group_offset) { group_offset_ = group_offset; }
|
||||
void set_regex(Regex regex) { regex_ = regex; }
|
||||
|
||||
size_t position() const { return offset_ + std::match_results<std::string::const_iterator>::position(); }
|
||||
size_t end_position() const { return position() + length(); }
|
||||
bool found() const { return not empty(); }
|
||||
const std::string str() const { return str(0); }
|
||||
const std::string str(int i) const { return std::match_results<std::string::const_iterator>::str(i + group_offset_); }
|
||||
Regex regex() const { return regex_; }
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
class MatchType: public Match {
|
||||
T type_;
|
||||
|
||||
public:
|
||||
MatchType(): Match() { }
|
||||
explicit MatchType(const Match& obj): Match(obj) { }
|
||||
MatchType(Match&& obj): Match(std::move(obj)) { }
|
||||
|
||||
void set_type(T type) { type_ = type; }
|
||||
|
||||
T type() const { return type_; }
|
||||
};
|
||||
|
||||
|
||||
class MatchClosed {
|
||||
public:
|
||||
Match open_match, close_match;
|
||||
|
||||
MatchClosed() { }
|
||||
MatchClosed(Match& open_match, Match& close_match): open_match(open_match), close_match(close_match) { }
|
||||
|
||||
size_t position() const { return open_match.position(); }
|
||||
size_t end_position() const { return close_match.end_position(); }
|
||||
size_t length() const { return close_match.end_position() - open_match.position(); }
|
||||
bool found() const { return open_match.found() and close_match.found(); }
|
||||
std::string prefix() const { return open_match.prefix().str(); }
|
||||
std::string suffix() const { return close_match.suffix().str(); }
|
||||
std::string outer() const { return open_match.str() + static_cast<std::string>(open_match.suffix()).substr(0, close_match.end_position() - open_match.end_position()); }
|
||||
std::string inner() const { return static_cast<std::string>(open_match.suffix()).substr(0, close_match.position() - open_match.end_position()); }
|
||||
};
|
||||
|
||||
|
||||
inline Match search(const std::string& input, const Regex& regex, size_t position) {
|
||||
if (position >= input.length()) { return Match(); }
|
||||
|
||||
Match match{position, regex};
|
||||
std::regex_search(input.cbegin() + position, input.cend(), match, regex);
|
||||
return match;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline MatchType<T> search(const std::string& input, const std::map<T, Regex>& regexes, size_t position) {
|
||||
// Map to vectors
|
||||
std::vector<T> class_vector;
|
||||
std::vector<Regex> regexes_vector;
|
||||
for (const auto& element: regexes) {
|
||||
class_vector.push_back(element.first);
|
||||
regexes_vector.push_back(element.second);
|
||||
}
|
||||
|
||||
// Regex join
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < regexes_vector.size(); ++i)
|
||||
{
|
||||
if (i != 0) { ss << ")|("; }
|
||||
ss << regexes_vector[i].pattern();
|
||||
}
|
||||
Regex regex{"(" + ss.str() + ")"};
|
||||
|
||||
MatchType<T> search_match = search(input, regex, position);
|
||||
if (not search_match.found()) { return MatchType<T>(); }
|
||||
|
||||
// Vector of id vs groups
|
||||
std::vector<unsigned int> regex_mark_counts = {};
|
||||
for (unsigned int i = 0; i < regexes_vector.size(); i++) {
|
||||
for (unsigned int j = 0; j < regexes_vector[i].mark_count() + 1; j++) {
|
||||
regex_mark_counts.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = 1; i < search_match.size(); i++) {
|
||||
if (search_match.length(i) > 0) {
|
||||
search_match.set_group_offset(i);
|
||||
search_match.set_type(class_vector[regex_mark_counts[i]]);
|
||||
search_match.set_regex(regexes_vector[regex_mark_counts[i]]);
|
||||
return search_match;
|
||||
}
|
||||
}
|
||||
|
||||
inja_throw("regex_search_error", "error while searching in input: " + input);
|
||||
return search_match;
|
||||
}
|
||||
|
||||
inline MatchClosed search_closed_on_level(const std::string& input, const Regex& regex_statement, const Regex& regex_level_up, const Regex& regex_level_down, const Regex& regex_search, Match open_match) {
|
||||
|
||||
int level {0};
|
||||
size_t current_position = open_match.end_position();
|
||||
Match match_delimiter = search(input, regex_statement, current_position);
|
||||
while (match_delimiter.found()) {
|
||||
current_position = match_delimiter.end_position();
|
||||
|
||||
const std::string inner = match_delimiter.str(1);
|
||||
if (std::regex_match(inner.cbegin(), inner.cend(), regex_search) and level == 0) { break; }
|
||||
if (std::regex_match(inner.cbegin(), inner.cend(), regex_level_up)) { level += 1; }
|
||||
else if (std::regex_match(inner.cbegin(), inner.cend(), regex_level_down)) { level -= 1; }
|
||||
|
||||
if (level < 0) { return MatchClosed(); }
|
||||
match_delimiter = search(input, regex_statement, current_position);
|
||||
}
|
||||
|
||||
return MatchClosed(open_match, match_delimiter);
|
||||
}
|
||||
|
||||
inline MatchClosed search_closed(const std::string& input, const Regex& regex_statement, const Regex& regex_open, const Regex& regex_close, Match& open_match) {
|
||||
return search_closed_on_level(input, regex_statement, regex_open, regex_close, regex_close, open_match);
|
||||
}
|
||||
|
||||
template<typename T, typename S>
|
||||
inline MatchType<T> match(const std::string& input, const std::map<T, Regex, S>& regexes) {
|
||||
MatchType<T> match;
|
||||
for (const auto& e: regexes) {
|
||||
if (std::regex_match(input.cbegin(), input.cend(), match, e.second)) {
|
||||
match.set_type(e.first);
|
||||
match.set_regex(e.second);
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_REGEX_HPP
|
||||
285
include/renderer.hpp
Normal file
285
include/renderer.hpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#ifndef PANTOR_INJA_RENDERER_HPP
|
||||
#define PANTOR_INJA_RENDERER_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
std::map<Parsed::CallbackSignature, std::function<json(const Parsed::Arguments&, const json&)>> map_callbacks;
|
||||
|
||||
template<bool>
|
||||
bool eval_expression(const Parsed::ElementExpression& element, const json& data) {
|
||||
const json var = eval_function(element, data);
|
||||
if (var.empty()) { return false; }
|
||||
else if (var.is_number()) { return (var != 0); }
|
||||
else if (var.is_string()) { return not var.empty(); }
|
||||
try {
|
||||
return var.get<bool>();
|
||||
} catch (json::type_error& e) {
|
||||
inja_throw("json_error", e.what());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T = json>
|
||||
T eval_expression(const Parsed::ElementExpression& element, const json& data) {
|
||||
const json var = eval_function(element, data);
|
||||
if (var.empty()) return T();
|
||||
try {
|
||||
return var.get<T>();
|
||||
} catch (json::type_error& e) {
|
||||
inja_throw("json_error", e.what());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
json eval_function(const Parsed::ElementExpression& element, const json& data) {
|
||||
switch (element.function) {
|
||||
case Parsed::Function::Upper: {
|
||||
std::string str = eval_expression<std::string>(element.args[0], data);
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
||||
return str;
|
||||
}
|
||||
case Parsed::Function::Lower: {
|
||||
std::string str = eval_expression<std::string>(element.args[0], data);
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
||||
return str;
|
||||
}
|
||||
case Parsed::Function::Range: {
|
||||
const int number = eval_expression<int>(element.args[0], data);
|
||||
std::vector<int> result(number);
|
||||
std::iota(std::begin(result), std::end(result), 0);
|
||||
return result;
|
||||
}
|
||||
case Parsed::Function::Length: {
|
||||
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
||||
return list.size();
|
||||
}
|
||||
case Parsed::Function::Sort: {
|
||||
std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
||||
std::sort(list.begin(), list.end());
|
||||
return list;
|
||||
}
|
||||
case Parsed::Function::First: {
|
||||
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
||||
return list.front();
|
||||
}
|
||||
case Parsed::Function::Last: {
|
||||
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
||||
return list.back();
|
||||
}
|
||||
case Parsed::Function::Round: {
|
||||
const double number = eval_expression<double>(element.args[0], data);
|
||||
const int precision = eval_expression<int>(element.args[1], data);
|
||||
return std::round(number * std::pow(10.0, precision)) / std::pow(10.0, precision);
|
||||
}
|
||||
case Parsed::Function::DivisibleBy: {
|
||||
const int number = eval_expression<int>(element.args[0], data);
|
||||
const int divisor = eval_expression<int>(element.args[1], data);
|
||||
return (divisor != 0) && (number % divisor == 0);
|
||||
}
|
||||
case Parsed::Function::Odd: {
|
||||
const int number = eval_expression<int>(element.args[0], data);
|
||||
return (number % 2 != 0);
|
||||
}
|
||||
case Parsed::Function::Even: {
|
||||
const int number = eval_expression<int>(element.args[0], data);
|
||||
return (number % 2 == 0);
|
||||
}
|
||||
case Parsed::Function::Max: {
|
||||
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
||||
return *std::max_element(list.begin(), list.end());
|
||||
}
|
||||
case Parsed::Function::Min: {
|
||||
const std::vector<json> list = eval_expression<std::vector<json>>(element.args[0], data);
|
||||
return *std::min_element(list.begin(), list.end());
|
||||
}
|
||||
case Parsed::Function::Not: {
|
||||
return not eval_expression<bool>(element.args[0], data);
|
||||
}
|
||||
case Parsed::Function::And: {
|
||||
return (eval_expression<bool>(element.args[0], data) and eval_expression<bool>(element.args[1], data));
|
||||
}
|
||||
case Parsed::Function::Or: {
|
||||
return (eval_expression<bool>(element.args[0], data) or eval_expression<bool>(element.args[1], data));
|
||||
}
|
||||
case Parsed::Function::In: {
|
||||
const json value = eval_expression(element.args[0], data);
|
||||
const json list = eval_expression(element.args[1], data);
|
||||
return (std::find(list.begin(), list.end(), value) != list.end());
|
||||
}
|
||||
case Parsed::Function::Equal: {
|
||||
return eval_expression(element.args[0], data) == eval_expression(element.args[1], data);
|
||||
}
|
||||
case Parsed::Function::Greater: {
|
||||
return eval_expression(element.args[0], data) > eval_expression(element.args[1], data);
|
||||
}
|
||||
case Parsed::Function::Less: {
|
||||
return eval_expression(element.args[0], data) < eval_expression(element.args[1], data);
|
||||
}
|
||||
case Parsed::Function::GreaterEqual: {
|
||||
return eval_expression(element.args[0], data) >= eval_expression(element.args[1], data);
|
||||
}
|
||||
case Parsed::Function::LessEqual: {
|
||||
return eval_expression(element.args[0], data) <= eval_expression(element.args[1], data);
|
||||
}
|
||||
case Parsed::Function::Different: {
|
||||
return eval_expression(element.args[0], data) != eval_expression(element.args[1], data);
|
||||
}
|
||||
case Parsed::Function::Float: {
|
||||
return std::stod(eval_expression<std::string>(element.args[0], data));
|
||||
}
|
||||
case Parsed::Function::Int: {
|
||||
return std::stoi(eval_expression<std::string>(element.args[0], data));
|
||||
}
|
||||
case Parsed::Function::ReadJson: {
|
||||
try {
|
||||
return data.at(json::json_pointer(element.command));
|
||||
} catch (std::exception&) {
|
||||
inja_throw("render_error", "variable '" + element.command + "' not found");
|
||||
}
|
||||
}
|
||||
case Parsed::Function::Result: {
|
||||
return element.result;
|
||||
}
|
||||
case Parsed::Function::Default: {
|
||||
try {
|
||||
return eval_expression(element.args[0], data);
|
||||
} catch (std::exception&) {
|
||||
return eval_expression(element.args[1], data);
|
||||
}
|
||||
}
|
||||
case Parsed::Function::Callback: {
|
||||
Parsed::CallbackSignature signature = std::make_pair(element.command, element.args.size());
|
||||
return map_callbacks.at(signature)(element.args, data);
|
||||
}
|
||||
case Parsed::Function::Exists: {
|
||||
const std::string name = eval_expression<std::string>(element.args[0], data);
|
||||
return data.find(name) != data.end();
|
||||
}
|
||||
case Parsed::Function::ExistsInObject: {
|
||||
const std::string name = eval_expression<std::string>(element.args[1], data);
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.find(name) != d.end();
|
||||
}
|
||||
case Parsed::Function::IsBoolean: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_boolean();
|
||||
}
|
||||
case Parsed::Function::IsNumber: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_number();
|
||||
}
|
||||
case Parsed::Function::IsInteger: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_number_integer();
|
||||
}
|
||||
case Parsed::Function::IsFloat: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_number_float();
|
||||
}
|
||||
case Parsed::Function::IsObject: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_object();
|
||||
}
|
||||
case Parsed::Function::IsArray: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_array();
|
||||
}
|
||||
case Parsed::Function::IsString: {
|
||||
const json d = eval_expression(element.args[0], data);
|
||||
return d.is_string();
|
||||
}
|
||||
}
|
||||
|
||||
inja_throw("render_error", "unknown function in renderer: " + element.command);
|
||||
return json();
|
||||
}
|
||||
|
||||
std::string render(Template temp, const json& data) {
|
||||
std::string result {""};
|
||||
for (const auto& element: temp.parsed_template().children) {
|
||||
switch (element->type) {
|
||||
case Parsed::Type::String: {
|
||||
auto element_string = std::static_pointer_cast<Parsed::ElementString>(element);
|
||||
result.append(element_string->text);
|
||||
break;
|
||||
}
|
||||
case Parsed::Type::Expression: {
|
||||
auto element_expression = std::static_pointer_cast<Parsed::ElementExpression>(element);
|
||||
const json variable = eval_expression(*element_expression, data);
|
||||
|
||||
if (variable.is_string()) {
|
||||
result.append( variable.get<std::string>() );
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << variable;
|
||||
result.append( ss.str() );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Parsed::Type::Loop: {
|
||||
auto element_loop = std::static_pointer_cast<Parsed::ElementLoop>(element);
|
||||
switch (element_loop->loop) {
|
||||
case Parsed::Loop::ForListIn: {
|
||||
const std::vector<json> list = eval_expression<std::vector<json>>(element_loop->list, data);
|
||||
for (unsigned int i = 0; i < list.size(); i++) {
|
||||
json data_loop = data;
|
||||
/* For nested loops, use parent/index */
|
||||
if (data_loop.count("loop") == 1) {
|
||||
data_loop["loop"]["parent"] = data_loop["loop"];
|
||||
}
|
||||
data_loop[element_loop->value] = list[i];
|
||||
data_loop["loop"]["index"] = i;
|
||||
data_loop["loop"]["index1"] = i + 1;
|
||||
data_loop["loop"]["is_first"] = (i == 0);
|
||||
data_loop["loop"]["is_last"] = (i == list.size() - 1);
|
||||
result.append( render(Template(*element_loop), data_loop) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Parsed::Loop::ForMapIn: {
|
||||
const std::map<std::string, json> map = eval_expression<std::map<std::string, json>>(element_loop->list, data);
|
||||
for (const auto& item: map) {
|
||||
json data_loop = data;
|
||||
data_loop[element_loop->key] = item.first;
|
||||
data_loop[element_loop->value] = item.second;
|
||||
result.append( render(Template(*element_loop), data_loop) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Parsed::Type::Condition: {
|
||||
auto element_condition = std::static_pointer_cast<Parsed::ElementConditionContainer>(element);
|
||||
for (const auto& branch: element_condition->children) {
|
||||
auto element_branch = std::static_pointer_cast<Parsed::ElementConditionBranch>(branch);
|
||||
if (element_branch->condition_type == Parsed::Condition::Else || eval_expression<bool>(element_branch->condition, data)) {
|
||||
result.append( render(Template(*element_branch), data) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_RENDERER_HPP
|
||||
19
include/template.hpp
Normal file
19
include/template.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef PANTOR_INJA_TEMPLATE_HPP
|
||||
#define PANTOR_INJA_TEMPLATE_HPP
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
class Template {
|
||||
Parsed::Element _parsed_template;
|
||||
|
||||
public:
|
||||
const Parsed::Element parsed_template() { return _parsed_template; }
|
||||
|
||||
explicit Template(): _parsed_template(Parsed::Element()) { }
|
||||
explicit Template(const Parsed::Element& parsed_template): _parsed_template(parsed_template) { }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_TEMPLATE_HPP
|
||||
16
include/utils.hpp
Normal file
16
include/utils.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef PANTOR_INJA_UTILS_HPP
|
||||
#define PANTOR_INJA_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace inja {
|
||||
/*!
|
||||
@brief render with default settings
|
||||
*/
|
||||
inline std::string render(const std::string& input, const json& data) {
|
||||
return Environment().render(input, data);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_UTILS_HPP
|
||||
19
meson.build
19
meson.build
@@ -1,7 +1,22 @@
|
||||
project('inja', 'cpp', default_options : ['cpp_std=c++11'])
|
||||
project('inja', 'cpp', default_options: ['cpp_std=c++11'])
|
||||
|
||||
|
||||
inja_dep = declare_dependency(
|
||||
include_directories : include_directories('src')
|
||||
include_directories: include_directories('include')
|
||||
)
|
||||
|
||||
inja_single_dep = declare_dependency(
|
||||
include_directories: include_directories('single_include', 'include')
|
||||
)
|
||||
|
||||
|
||||
# Amalgamate inja header files
|
||||
r = run_command('python3', 'amalgamate/amalgamate.py', '-c', 'amalgamate/config.json', '-s', 'include')
|
||||
if r.returncode() != 0
|
||||
message(r.stdout().strip())
|
||||
else
|
||||
message('Amalgamated inja header files.')
|
||||
endif
|
||||
|
||||
|
||||
subdir('test')
|
||||
|
||||
@@ -33,24 +33,18 @@ SOFTWARE.
|
||||
#define PANTOR_INJA_VERSION_PATCH 0
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
|
||||
|
||||
// #include "error.hpp"
|
||||
#ifndef PANTOR_INJA_ERROR_HPP
|
||||
#define PANTOR_INJA_ERROR_HPP
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
/*!
|
||||
@brief throw an error with a given message
|
||||
*/
|
||||
@@ -58,6 +52,20 @@ inline void inja_throw(const std::string& type, const std::string& message) {
|
||||
throw std::runtime_error("[inja.exception." + type + "] " + message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_ERROR_HPP
|
||||
|
||||
// #include "regex.hpp"
|
||||
#ifndef PANTOR_INJA_REGEX_HPP
|
||||
#define PANTOR_INJA_REGEX_HPP
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
/*!
|
||||
@brief inja regex class, saves string pattern in addition to std::regex
|
||||
@@ -217,6 +225,22 @@ inline MatchType<T> match(const std::string& input, const std::map<T, Regex, S>&
|
||||
return match;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_REGEX_HPP
|
||||
|
||||
// #include "parsed.hpp"
|
||||
#ifndef PANTOR_INJA_PARSED_HPP
|
||||
#define PANTOR_INJA_PARSED_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
enum class ElementNotation {
|
||||
Dot,
|
||||
@@ -357,6 +381,16 @@ struct Parsed {
|
||||
using CallbackSignature = std::pair<std::string, size_t>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_PARSED_HPP
|
||||
|
||||
// #include "template.hpp"
|
||||
#ifndef PANTOR_INJA_TEMPLATE_HPP
|
||||
#define PANTOR_INJA_TEMPLATE_HPP
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
class Template {
|
||||
Parsed::Element _parsed_template;
|
||||
@@ -368,6 +402,23 @@ public:
|
||||
explicit Template(const Parsed::Element& parsed_template): _parsed_template(parsed_template) { }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_TEMPLATE_HPP
|
||||
|
||||
// #include "renderer.hpp"
|
||||
#ifndef PANTOR_INJA_RENDERER_HPP
|
||||
#define PANTOR_INJA_RENDERER_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
@@ -638,6 +689,27 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_RENDERER_HPP
|
||||
|
||||
// #include "parser.hpp"
|
||||
#ifndef PANTOR_INJA_PARSER_HPP
|
||||
#define PANTOR_INJA_PARSER_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <regex.hpp>
|
||||
#include <template.hpp>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
@@ -964,6 +1036,27 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_PARSER_HPP
|
||||
|
||||
// #include "environment.hpp"
|
||||
#ifndef PANTOR_INJA_ENVIRONMENT_HPP
|
||||
#define PANTOR_INJA_ENVIRONMENT_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <parser.hpp>
|
||||
#include <renderer.hpp>
|
||||
#include <template.hpp>
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
/*!
|
||||
@brief Environment class
|
||||
@@ -1074,14 +1167,28 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
@brief render with default settings
|
||||
*/
|
||||
inline std::string render(const std::string& input, const json& data) {
|
||||
return Environment().render(input, data);
|
||||
}
|
||||
|
||||
} // namespace inja
|
||||
#endif // PANTOR_INJA_ENVIRONMENT_HPP
|
||||
|
||||
// #include "utils.hpp"
|
||||
#ifndef PANTOR_INJA_UTILS_HPP
|
||||
#define PANTOR_INJA_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace inja {
|
||||
/*!
|
||||
@brief render with default settings
|
||||
*/
|
||||
inline std::string render(const std::string& input, const json& data) {
|
||||
return Environment().render(input, data);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PANTOR_INJA_UTILS_HPP
|
||||
|
||||
|
||||
|
||||
#endif // PANTOR_INJA_HPP
|
||||
@@ -21,6 +21,13 @@ add_executable(inja_test
|
||||
src/unit.cpp
|
||||
)
|
||||
|
||||
add_executable(inja_single_test
|
||||
src/unit-files.cpp
|
||||
src/unit-renderer.cpp
|
||||
src/unit-string-helper.cpp
|
||||
src/unit.cpp
|
||||
)
|
||||
|
||||
add_executable(inja_benchmark
|
||||
src/benchmark.cpp
|
||||
)
|
||||
@@ -51,6 +58,7 @@ else() # Manage dependencies manually
|
||||
|
||||
# Add dependencies to targets
|
||||
target_link_libraries(inja_test Catch inja)
|
||||
target_link_libraries(inja_single_test Catch inja_single)
|
||||
target_link_libraries(inja_benchmark hayai inja)
|
||||
endif()
|
||||
|
||||
@@ -73,3 +81,8 @@ add_test(NAME inja_test
|
||||
COMMAND inja_test
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_test(NAME inja_single_test
|
||||
COMMAND inja_single_test
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
@@ -7,6 +7,15 @@ unit_test = executable(
|
||||
dependencies: inja_dep
|
||||
)
|
||||
|
||||
unit_single_test = executable(
|
||||
'inja-single-test',
|
||||
'src/unit.cpp',
|
||||
'src/unit-files.cpp',
|
||||
'src/unit-renderer.cpp',
|
||||
'src/unit-string-helper.cpp',
|
||||
dependencies: inja_single_dep
|
||||
)
|
||||
|
||||
inja_benchmark = executable(
|
||||
'inja_benchmark',
|
||||
'src/benchmark.cpp',
|
||||
@@ -14,3 +23,4 @@ inja_benchmark = executable(
|
||||
)
|
||||
|
||||
test('Inja unit test', unit_test)
|
||||
test('Inja single unit test', unit_single_test)
|
||||
|
||||
Reference in New Issue
Block a user