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.

317 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# vim: fileencoding=utf-8
#
# ToDo: добавить проверку того, полностью ли парсился документ. Если отпарсился
# не весь файл -- выдаем ошибку.
#
from .base_format import Format
from collections import OrderedDict
from pyparsing import originalTextFor, OneOrMore, Word, alphanums, Literal,\
ZeroOrMore, Forward, Optional, Group, restOfLine,\
cppStyleComment, Keyword, printables, nums, SkipTo
class BINDFormat(Format):
'''Класс формата BIND. В отличие от большинства других форматов обрабатывает
документ не построчно, а напрямую применяя парсер ко всему тексту
документа.'''
FORMAT = 'bind'
EXECUTABLE = False
comment_symbol = '#'
def __init__(self, document_text: str,
template_path,
ignore_comments=False,
join_before=False,
add_header=False,
already_changed=False,
**kwargs):
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 add_header and not ignore_comments:
self.header, document_text = self._get_header_and_document_text(
document_text,
template_path,
already_changed=already_changed)
else:
self.header = ''
document_text = document_text.strip()
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