|
|
# vim: fileencoding=utf-8
|
|
|
#
|
|
|
# ToDo: добавить проверку того, полностью ли парсился документ. Если отпарсился
|
|
|
# не весь файл -- выдаем ошибку.
|
|
|
#
|
|
|
from .base_format import BaseFormat
|
|
|
from ..template_engine import ParametersContainer
|
|
|
from collections import OrderedDict
|
|
|
from pyparsing import originalTextFor, OneOrMore, Word, alphanums, Literal,\
|
|
|
ZeroOrMore, Forward, Optional, Group, restOfLine,\
|
|
|
cppStyleComment, Keyword, printables, nums, SkipTo
|
|
|
|
|
|
|
|
|
class BINDFormat(BaseFormat):
|
|
|
'''Класс формата BIND. В отличие от большинства других форматов обрабатывает
|
|
|
документ не построчно, а напрямую применяя парсер ко всему тексту
|
|
|
документа.'''
|
|
|
FORMAT = 'bind'
|
|
|
EXECUTABLE = False
|
|
|
|
|
|
def __init__(self, document_text: str,
|
|
|
ignore_comments=False,
|
|
|
join_before=False,
|
|
|
parameters=ParametersContainer()):
|
|
|
processing_methods = []
|
|
|
|
|
|
super().__init__(processing_methods)
|
|
|
|
|
|
self._ignore_comments = ignore_comments
|
|
|
self._comments_processing = True
|
|
|
self._join_before = join_before
|
|
|
|
|
|
self._last_comments_list = []
|
|
|
self._initialize_parser()
|
|
|
|
|
|
if document_text == '':
|
|
|
self._document_dictionary = OrderedDict()
|
|
|
else:
|
|
|
self._parse_text(document_text)
|
|
|
|
|
|
def _initialize_parser(self):
|
|
|
'''Метод для инициализации парсеров. Для данного формата парсеры
|
|
|
инициализируется при создании экземпляра формата, поскольку настройка
|
|
|
парсеров зависит от того, включен ли флаг ignore_comments.'''
|
|
|
left_brace = Literal('{')
|
|
|
right_brace = Literal('}')
|
|
|
semicolon = Literal(';')
|
|
|
action_symbols = (Literal('!') |
|
|
|
Literal('-'))
|
|
|
|
|
|
plain_allow = Keyword('allow')
|
|
|
drop_allow = Keyword('!allow')
|
|
|
replace_allow = Keyword('-allow')
|
|
|
allow = (plain_allow | drop_allow | replace_allow)
|
|
|
keys = Keyword('keys')
|
|
|
inet = Keyword('inet')
|
|
|
|
|
|
statement = originalTextFor(
|
|
|
Word(alphanums+'_-',
|
|
|
excludeChars='{};')
|
|
|
)
|
|
|
|
|
|
statement_name = originalTextFor(
|
|
|
Word(printables,
|
|
|
excludeChars='{};')
|
|
|
)
|
|
|
|
|
|
statement_class = originalTextFor(
|
|
|
Word(printables,
|
|
|
excludeChars='{};')
|
|
|
)
|
|
|
|
|
|
parameter_value = originalTextFor(
|
|
|
Word(printables,
|
|
|
excludeChars='{};')
|
|
|
)('parameter')
|
|
|
|
|
|
ip_value = originalTextFor(
|
|
|
Word(nums+':./',
|
|
|
excludeChars=';{}')
|
|
|
)
|
|
|
|
|
|
# Будущий парсер блока.
|
|
|
block = Forward()
|
|
|
|
|
|
# Для парсинга директивы inet_spec.
|
|
|
allow_item = (ip_value | statement_name) + semicolon.suppress()
|
|
|
key_item = statement_name + semicolon.suppress()
|
|
|
|
|
|
allow_group = Group(Group(Optional(action_symbols,
|
|
|
default='')('action')
|
|
|
+ allow)
|
|
|
+ Group(left_brace.suppress()
|
|
|
+ ZeroOrMore(allow_item)
|
|
|
+ right_brace.suppress()))
|
|
|
|
|
|
keys_group = Group(Group(Optional(action_symbols, default='')('action')
|
|
|
+ keys)
|
|
|
+ Group(left_brace.suppress()
|
|
|
+ ZeroOrMore(key_item)
|
|
|
+ right_brace.suppress()))
|
|
|
|
|
|
inet_spec = (Group(Optional(action_symbols, default='')('action')
|
|
|
+ inet + originalTextFor(SkipTo(allow,
|
|
|
include=False)
|
|
|
)('parameter')
|
|
|
)('name')
|
|
|
+ Group(allow_group
|
|
|
+ Optional(keys_group)
|
|
|
+ semicolon.suppress())('content')
|
|
|
).setParseAction(
|
|
|
self._add_inet_specline
|
|
|
)
|
|
|
|
|
|
# Для парсинга комментариев.
|
|
|
python_style_comment = originalTextFor(Literal('#') + restOfLine)
|
|
|
comments = (cppStyleComment |
|
|
|
python_style_comment).setParseAction(
|
|
|
self._create_comment_list
|
|
|
)
|
|
|
|
|
|
# Для парсинга директивы include.
|
|
|
include_line = (Optional(action_symbols, default='')('action')
|
|
|
+ Keyword('include')
|
|
|
+ Word(printables, excludeChars=';{}')
|
|
|
+ Optional(semicolon.suppress())
|
|
|
).setParseAction(
|
|
|
self._add_include_line
|
|
|
)
|
|
|
|
|
|
# Для парсинга простых директив состоящих из одного
|
|
|
# или двух параметров.
|
|
|
plain_line = (Group(Optional(action_symbols, default='')('action')
|
|
|
+ statement)('name')
|
|
|
+ Optional(parameter_value)
|
|
|
+ Optional(semicolon.suppress())).setParseAction(
|
|
|
self._add_plain_line
|
|
|
)
|
|
|
|
|
|
# Метод для парсинга IP адресов.
|
|
|
ip_line = (Group(Optional(action_symbols, default='')('action')
|
|
|
+ ip_value
|
|
|
+ Optional(semicolon.suppress()))('IP')
|
|
|
).setParseAction(self._add_ipline)
|
|
|
|
|
|
# Парсеры параметров.
|
|
|
param_line = (include_line |
|
|
|
ip_line |
|
|
|
plain_line)
|
|
|
|
|
|
# Парсер блока параметров.
|
|
|
param_block = (Group(Optional(action_symbols, default='')('action')
|
|
|
+ statement + Optional(statement_name)
|
|
|
+ Optional(statement_class))('name')
|
|
|
+ block('content')
|
|
|
+ Optional(semicolon.suppress())).setParseAction(
|
|
|
self._add_param_block
|
|
|
)
|
|
|
|
|
|
# Виды блочных директив.
|
|
|
block_types = (inet_spec | param_block)
|
|
|
|
|
|
# Парсер параметров с комментариями.
|
|
|
# Note: Применение parser.ignore(comments) является причиной странного
|
|
|
# поведения парсера, при котором невозможно многократное повторное
|
|
|
# применение формата после установки флага ignore_comments.
|
|
|
if self._ignore_comments:
|
|
|
param_line_with_comments = (ZeroOrMore(comments).suppress()(
|
|
|
'comments'
|
|
|
)
|
|
|
+ param_line('value')
|
|
|
).setParseAction(
|
|
|
self._add_comments_to_paramline
|
|
|
)
|
|
|
else:
|
|
|
param_line_with_comments = (ZeroOrMore(comments)('comments')
|
|
|
+ param_line('value')
|
|
|
).setParseAction(
|
|
|
self._add_comments_to_paramline
|
|
|
)
|
|
|
|
|
|
# Парсер блока с комментариями.
|
|
|
if self._ignore_comments:
|
|
|
param_block_with_comments = (ZeroOrMore(comments).suppress()(
|
|
|
'comments'
|
|
|
)
|
|
|
+ block_types('value')
|
|
|
).setParseAction(
|
|
|
self._add_comments_to_block
|
|
|
)
|
|
|
else:
|
|
|
param_block_with_comments = (ZeroOrMore(comments)('comments')
|
|
|
+ block_types('value')
|
|
|
).setParseAction(
|
|
|
self._add_comments_to_block
|
|
|
)
|
|
|
|
|
|
# Парсер содержимого блоков.
|
|
|
block_item = (param_block_with_comments |
|
|
|
param_line_with_comments)
|
|
|
|
|
|
# Для парсинга всего блока с любым содержимым.
|
|
|
block << Group(left_brace.suppress() + ZeroOrMore(block_item)
|
|
|
+ right_brace.suppress())
|
|
|
|
|
|
# Парсер всего документа.
|
|
|
self._document_parser = OneOrMore(block_item)
|
|
|
|
|
|
def _parse_text(self, text):
|
|
|
'''Метод для запуска разбора документа.'''
|
|
|
parsing_result = self._document_parser.parseString(text, parseAll=True)
|
|
|
list_of_elements = parsing_result.asList()
|
|
|
|
|
|
# На выходе парсера получаем список словарей секций, который затем
|
|
|
# преобразуем в словарь документа.
|
|
|
for part in list_of_elements:
|
|
|
self._join_dictionary(self._document_dictionary,
|
|
|
part)
|
|
|
|
|
|
def _join_dictionary(self, out_dictionary, dictionary_to_add):
|
|
|
'''Метод для преобразования результата парсинга в итоговый словарь.
|
|
|
Работает рекурсивно. Умеет объединять секции с одинаковыми названиями.
|
|
|
'''
|
|
|
for key in dictionary_to_add:
|
|
|
if dictionary_to_add == OrderedDict():
|
|
|
return
|
|
|
if key in out_dictionary and\
|
|
|
isinstance(dictionary_to_add[key], OrderedDict) and\
|
|
|
isinstance(out_dictionary[key], OrderedDict):
|
|
|
self._join_dictionary(out_dictionary[key],
|
|
|
dictionary_to_add[key])
|
|
|
else:
|
|
|
out_dictionary[key] = dictionary_to_add[key]
|
|
|
|
|
|
def _add_plain_line(self, current_parse):
|
|
|
'''Метод используемый в парсере простых строк, состоящих только из
|
|
|
имени параметра и опционально из его значения.'''
|
|
|
name = tuple(current_parse.name.asList())
|
|
|
if not current_parse.parameter == '':
|
|
|
value = current_parse.parameter
|
|
|
else:
|
|
|
if current_parse.name.action == '-':
|
|
|
return OrderedDict()
|
|
|
value = ''
|
|
|
|
|
|
return OrderedDict({name: [value]})
|
|
|
|
|
|
def _add_include_line(self, current_parse):
|
|
|
'''Метод используемый в парсере директивы include.'''
|
|
|
name = current_parse.asList()
|
|
|
return OrderedDict({tuple(name): ['']})
|
|
|
|
|
|
def _add_ipline(self, current_parse):
|
|
|
'''Метод используемый в парсере ip адресов.'''
|
|
|
ip_value = current_parse.IP
|
|
|
return OrderedDict({tuple(ip_value): ['']})
|
|
|
|
|
|
def _add_inet_specline(self, current_parse):
|
|
|
'''Метод используемый в парсере директивы inet_spec.'''
|
|
|
# Удаляем пробельные символы из второго параметра директивы.
|
|
|
current_parse.name.parameter = current_parse.name.parameter.strip()
|
|
|
block_name = tuple(current_parse.name.asList())
|
|
|
block_content = current_parse.content.asList()
|
|
|
content = OrderedDict({'#': []})
|
|
|
for item in block_content:
|
|
|
current_keyword, values = item
|
|
|
current_keyword = tuple(current_keyword)
|
|
|
content[current_keyword] = values
|
|
|
return OrderedDict({block_name: content})
|
|
|
|
|
|
def _add_param_block(self, current_parse):
|
|
|
'''Метод используемый в парсере блоков параметров. Использует
|
|
|
рекурсивный метод self._join_dictionary для построения словаря блока
|
|
|
параметров. Учитывает возможность наличия вложенных блоков.'''
|
|
|
block_name = tuple(current_parse.name.asList())
|
|
|
block_content = current_parse.content.asList()
|
|
|
content = OrderedDict({'#': []})
|
|
|
|
|
|
for item in block_content:
|
|
|
self._join_dictionary(content, item)
|
|
|
return OrderedDict({block_name: content})
|
|
|
|
|
|
def _add_comments_to_paramline(self, current_parse):
|
|
|
'''Метод используемый в парсере директив вместе с относящимися к ним
|
|
|
комментариями. Закрепляет комментарии за данным параметром в итоговом
|
|
|
словаре.'''
|
|
|
[parameter] = current_parse.value.asList()
|
|
|
comments = current_parse.comments
|
|
|
if parameter == OrderedDict():
|
|
|
if not comments == '':
|
|
|
self._last_comments_list.extend(comments.asList())
|
|
|
return OrderedDict()
|
|
|
|
|
|
name = next(iter(parameter))
|
|
|
value = parameter[name]
|
|
|
|
|
|
if not comments == '':
|
|
|
comments_list = comments.asList()
|
|
|
parameter[name] = (self._last_comments_list
|
|
|
+ comments_list + value)
|
|
|
self._last_comments_list = []
|
|
|
return parameter
|
|
|
|
|
|
def _add_comments_to_block(self, current_parse):
|
|
|
'''Метод используемый в парсере блоков директив вместе с относящимися
|
|
|
к ним комментариями. Закрепляет комментарии за данным параметром в
|
|
|
итоговом словаре.'''
|
|
|
[value] = current_parse.value
|
|
|
[block_name] = value
|
|
|
block = value[block_name]
|
|
|
|
|
|
if not current_parse.comments == '':
|
|
|
block['#'] = (self._last_comments_list
|
|
|
+ current_parse.comments.asList())
|
|
|
self._last_comments_list = []
|
|
|
else:
|
|
|
block.pop('#')
|
|
|
return value
|
|
|
|
|
|
def _create_comment_list(self, current_parse):
|
|
|
'''Метод используемый в парсере комментариев. Формирует из обнаруженных
|
|
|
комментариев список, который затем закрепляется за блоком или
|
|
|
параметром.'''
|
|
|
comments_list = []
|
|
|
comments = current_parse.asList()
|
|
|
for comment in comments:
|
|
|
lines = comment.splitlines()
|
|
|
for line in lines:
|
|
|
comments_list.append(line.strip())
|
|
|
return comments_list
|