You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

299 lines
11 KiB

from jinja2.ext import Extension
from jinja2 import Environment, FileSystemLoader, TemplateSyntaxError, nodes
from jinja2.utils import missing
from jinja2.runtime import Context, Undefined
from collections.abc import MutableMapping
class Variables(MutableMapping):
'''Класс заглушка вместо модуля переменных.'''
def __init__(self, *args, **kwargs):
self.__attrs = dict(*args, **kwargs)
def __next__(self):
iterator = iter(self.__attrs)
return next(iterator)
def __getattribute__(self, name):
if name == '_Variables__attrs':
return super().__getattribute__(name)
try:
return self.__attrs[name]
except KeyError:
raise AttributeError(name)
def __getitem__(self, name):
return self.__attrs[name]
def __setitem__(self, name, value):
self.__attrs[name] = value
def __delitem__(self, name):
del self.__attrs[name]
def __iter__(self):
return iter(self.__attrs)
def __len__(self):
return len(self.__attrs)
def __repr__(self):
return '<Variables {}>'.format(self.__attrs)
def resolve_or_missing(context, key, missing=missing, env={}):
datavars = context.parent['__datavars__']
if key in context.vars:
return context.vars[key]
if key in context.parent:
return context.parent[key]
if key in datavars:
return datavars[key]
for name in env:
if name in datavars and key in datavars[name]:
return datavars[name][key]
return missing
class CalculateContext(Context):
_env_set = set()
def resolve(self, key):
if self._legacy_resolve_mode:
rv = resolve_or_missing(self, key,
env=self._env_set)
else:
rv = self.resolve_or_missing(key)
if rv is missing:
return self.environment.undefined(name=key)
return rv
def resolve_or_missing(self, key):
if self._legacy_resolve_mode:
rv = self.resolve(key)
if isinstance(rv, Undefined):
rv = missing
return rv
return resolve_or_missing(self, key,
env=self._env_set)
class Parameters(MutableMapping):
'''Класс для хранения параметров и условий, взятых из шаблона, и передачи
их шаблонизатору.'''
def __init__(self, parameters_dictionary={}, condition=True):
self.__parameters = parameters_dictionary
self.__condition = condition
def set_parameters(self, *args, **kwargs):
parameters = dict(*args, **kwargs)
self.__parameters.update(parameters)
def set_condition(self, condition):
self.__condition = self.__condition and condition
def __getitem__(self, name):
return self.__parameters[name]
def __setitem__(self, name, value):
self.__parameters[name] = value
def __delitem__(self, name):
del self.__parameters[name]
def __iter__(self):
return iter(self.__parameters)
def __len__(self):
return len(self.__parameters)
def __repr__(self):
return '<Parameters: {0}, condition={1}>'.format(self.__parameters,
self.__condition)
@property
def condition(self):
return self.__condition
@property
def parameters(self):
return self.__parameters
class CalculateExtension(Extension):
_parameters_set = set()
def __init__(self, environment):
self.tags = {'calculate', 'save', 'set_var'}
self.CONDITION_TOKENS_TYPES = {'eq', 'ne', 'lt', 'gt', 'lteq', 'gteq'}
self.LITERAL_TOKENS_TYPES = {'string', 'integer', 'float'}
self.TARGET_FILES_SET = {'grp', 'system', 'etc', 'local', 'remote'}
self.parse_methods = {'calculate': self.parse_calculate,
'save': self.parse_save}
def parse(self, parser):
self.parser = parser
self.stream = parser.stream
tag_token = self.stream.current.value
return [self.parse_methods[tag_token]()]
def parse_save(self):
lineno = next(self.stream).lineno
target_file = nodes.Const('', lineno=lineno)
if self.stream.skip_if('dot'):
target_file_name = self.stream.expect('name').value
if target_file_name in self.TARGET_FILES_SET:
target_file = nodes.Const(target_file_name)
else:
TemplateSyntaxError("Unknown target file '{}'".
format(target_file_name),
lineno=lineno)
# получаем список из имени переменной.
module_name = self.stream.expect('name').value
variable_name = [nodes.Const(module_name, lineno=lineno)]
while self.stream.skip_if('dot'):
name = self.stream.expect('name').value
variable_name.append(nodes.Const(name, lineno=lineno))
variable_name = nodes.List(variable_name, lineno=lineno)
if self.stream.skip_if('assign'):
right_value = self.parser.parse_expression(with_condexpr=True)
save_variable_node = self.call_method('save_variable',
[variable_name,
right_value,
target_file,
nodes.ContextReference()],
lineno=lineno)
return nodes.Output([save_variable_node], lineno=lineno)
else:
TemplateSyntaxError("'=' is expected in 'save' tag.",
lineno=lineno)
def parse_calculate(self):
pairs_list = []
expect_comma_flag = False
conditions = nodes.Const(True)
lineno = next(self.stream).lineno
while self.stream.current.type != 'block_end':
if expect_comma_flag:
self.stream.expect('comma')
if self.stream.current.type == 'name':
if (self.stream.current.value in self._parameters_set and
self.stream.look().type != 'dot'):
pairs_list.append(self.get_parameter_node())
else:
conditions = nodes.And(
self.parser.parse_expression(
with_condexpr=True
),
conditions
)
else:
raise TemplateSyntaxError('Name is expected in calculate tag.',
lineno=self.stream.current.lineno)
expect_comma_flag = True
dictionary_node = nodes.Dict(pairs_list)
save_node = self.call_method('save_parameters',
[dictionary_node,
conditions,
nodes.ContextReference()],
lineno=lineno)
return nodes.Output([save_node], lineno=lineno)
def save_variable(self, variable_name, right_value, target_file, context):
'''Временный метод для сохранения значений переменных.'''
datavars = context.parent['__datavars__']
module_name = variable_name[0]
namespaces = variable_name[1:-1]
variable_name = variable_name[-1]
if module_name in datavars:
variables_module = datavars[module_name]
for namespace in namespaces:
if namespace not in variables_module:
variables_module[namespace] = Variables()
variables_module = variables_module[namespace]
variables_module[variable_name] = right_value
else:
AttributeError("Unknown variables module '{}'".
format(module_name))
return ''
def get_parameter_node(self):
lineno = self.stream.current.lineno
parameter_name = self.stream.expect('name').value
parameter_name_node = nodes.Const(parameter_name, lineno=lineno)
if self.stream.skip_if('assign'):
parameter_value = self.stream.current.value
parameter_rvalue = self.parser.parse_expression(with_condexpr=True)
if parameter_name == 'env':
env_names = parameter_value.split(',')
for name in env_names:
CalculateContext._env_set.add(name.strip())
else:
parameter_rvalue = nodes.Const(True, lineno=lineno)
return nodes.Pair(parameter_name_node, parameter_rvalue)
def save_parameters(cls, parameters_dictionary, compare_result, context):
context.parent['__parameters__'].set_parameters(parameters_dictionary)
context.parent['__parameters__'].set_condition(compare_result)
return ''
class TemplateEngine():
def __init__(self, directory_path='/',
parameters_set=set(),
env_set=set(),
datavars_module=Variables()):
CalculateExtension._parameters_set = parameters_set
self._datavars_module = datavars_module
self._parameters_object = Parameters()
self._template_text = ''
self.environment = Environment(loader=FileSystemLoader(directory_path),
extensions=[CalculateExtension])
self.environment.context_class = CalculateContext
def change_directory(self, directory_path):
self.environment.loader = FileSystemLoader(directory_path)
def process_template(self, template_path, env=set()):
CalculateContext._env_set = env
template = self.environment.get_template(template_path)
self._parameters_object = Parameters(parameters_dictionary={},
condition=True)
self._template_text = template.render(
__datavars__=self._datavars_module,
__parameters__=self._parameters_object
)
def process_template_from_string(self, string, env=set()):
CalculateContext._env_set = env
template = self.environment.from_string(string)
self._parameters_object = Parameters(parameters_dictionary={},
condition=True)
self._template_text = template.render(
__datavars__=self._datavars_module,
__parameters__=self._parameters_object
)
@property
def condition(self):
return self._parameters_object.condition
@property
def parameters(self):
return self._parameters_object.parameters
@property
def template_text(self):
text, self._template_text = self._template_text, ''
return text