|
|
# vim: fileencoding=utf-8
|
|
|
#
|
|
|
# ToDo: написать счетчик скобок для финальной оценки корректности
|
|
|
# документа.
|
|
|
#
|
|
|
from .base_format import BaseFormat
|
|
|
from collections import OrderedDict
|
|
|
from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\
|
|
|
OneOrMore, alphanums, ParseException, pyparsing_unicode,\
|
|
|
Group, Optional, alphas, lineEnd, lineStart, Keyword
|
|
|
|
|
|
|
|
|
class DovecotFormat(BaseFormat):
|
|
|
_initialized = False
|
|
|
_comment_symbol = ''
|
|
|
|
|
|
def __init__(self, document_text: str, ignore_comments=False,
|
|
|
join_before=False, comment_symbol=''):
|
|
|
processing_methods = [self._parse_comment_line,
|
|
|
self._parse_section_start_line,
|
|
|
self._parse_include_line,
|
|
|
self._parse_section_end_line,
|
|
|
self._parse_parameter_line,
|
|
|
self._parse_parameter_to_delete_line]
|
|
|
|
|
|
super().__init__(processing_methods)
|
|
|
self._ignore_comments = ignore_comments
|
|
|
self._comments_processing = True
|
|
|
self._need_finish = True
|
|
|
self._join_before = join_before
|
|
|
self._format = 'dovecot'
|
|
|
|
|
|
self._section_stack = OrderedDict()
|
|
|
self._current_section_name = ''
|
|
|
|
|
|
self._last_comments_list = []
|
|
|
if not self._initialized or comment_symbol != self._comment_symbol:
|
|
|
self._initialize_parser(comment_symbol)
|
|
|
|
|
|
if document_text == '':
|
|
|
self._document_dictionary = OrderedDict()
|
|
|
else:
|
|
|
document_lines = self._get_list_of_logic_lines(document_text)
|
|
|
self._lines_to_dictionary(document_lines)
|
|
|
|
|
|
@classmethod
|
|
|
def _initialize_parser(cls, comment_symbol=''):
|
|
|
# Знаки пунктуации и действий.
|
|
|
left_brace = Literal('{')
|
|
|
right_brace = Literal('}')
|
|
|
action_symbols = (Literal('!') | Literal('-'))
|
|
|
|
|
|
if not comment_symbol:
|
|
|
cls._comment_line_parser = originalTextFor(
|
|
|
Literal('#')
|
|
|
+ ZeroOrMore(Word(
|
|
|
printables
|
|
|
+ pyparsing_unicode.alphanums)
|
|
|
)
|
|
|
)('comment')
|
|
|
else:
|
|
|
cls._comment_line_parser = originalTextFor(
|
|
|
(Literal('#') | Literal(comment_symbol))
|
|
|
+ ZeroOrMore(Word(
|
|
|
printables
|
|
|
+ pyparsing_unicode.alphanums)
|
|
|
)
|
|
|
)('comment')
|
|
|
|
|
|
# Для парсинга строк с началом секций.
|
|
|
section = Word(alphas, alphanums+'-_', excludeChars='{}')
|
|
|
|
|
|
section_name = Word(printables, excludeChars='{}')
|
|
|
|
|
|
cls._section_start_parser = Group(Optional(action_symbols,
|
|
|
default='')('action')
|
|
|
+ section
|
|
|
+ Optional(section_name)
|
|
|
+ left_brace.suppress())('name')
|
|
|
|
|
|
# Для парсинга строк, указывающих конец секций.
|
|
|
cls._section_end_parser = lineStart() + right_brace + lineEnd()
|
|
|
|
|
|
# Для парсинга строк, содержащих параметры.
|
|
|
parameter_name = Word(alphas, alphanums+'_-', excludeChars='{}=')
|
|
|
|
|
|
parameter_value = OneOrMore(Word(printables))
|
|
|
|
|
|
cls._parameter_line_parser = (Group(Optional(action_symbols,
|
|
|
default='')('action')
|
|
|
+ parameter_name)('name')
|
|
|
+ Literal('=').suppress()
|
|
|
+ originalTextFor(
|
|
|
parameter_value
|
|
|
)('value'))
|
|
|
|
|
|
# Для парсинга строк с параметрами, подлежащими удалению.
|
|
|
cls._parameter_to_delete_parser = (action_symbols('action')
|
|
|
+ parameter_name
|
|
|
+ Optional(Literal('=')).suppress()
|
|
|
)
|
|
|
|
|
|
# Для парсинга строк, содержащих директиву !include.
|
|
|
include = Keyword('!include') | Keyword('!include_try')
|
|
|
|
|
|
include_line_plain = (Optional(~action_symbols, default='')('action')
|
|
|
+ include('keyword') + Word(printables)('value'))
|
|
|
|
|
|
include_line_to_delete = (action_symbols('action') + include('keyword')
|
|
|
+ Word(printables)('value'))
|
|
|
|
|
|
cls._include_line_parser = (include_line_plain |
|
|
|
include_line_to_delete)
|
|
|
cls._initialized = True
|
|
|
|
|
|
def _parse_section_start_line(self, line):
|
|
|
try:
|
|
|
parsing_result = self._section_start_parser.parseString(line)
|
|
|
self._match = True
|
|
|
|
|
|
section_name = tuple(parsing_result.name.asList())
|
|
|
|
|
|
if not self._last_comments_list == []:
|
|
|
section_content = OrderedDict({'#': self._last_comments_list})
|
|
|
self._last_comments_list = []
|
|
|
else:
|
|
|
section_content = OrderedDict()
|
|
|
|
|
|
new_section = OrderedDict({section_name: section_content})
|
|
|
|
|
|
if self._section_stack == OrderedDict():
|
|
|
if section_name in self._document_dictionary:
|
|
|
new_section = OrderedDict(
|
|
|
{section_name:
|
|
|
self._document_dictionary[section_name]}
|
|
|
)
|
|
|
else:
|
|
|
if section_name in \
|
|
|
self._section_stack[self._current_section_name]:
|
|
|
new_section = OrderedDict({
|
|
|
section_name:
|
|
|
self._section_stack[self._current_section_name]
|
|
|
[section_name]
|
|
|
})
|
|
|
else:
|
|
|
self._section_stack[self._current_section_name].update(
|
|
|
new_section
|
|
|
)
|
|
|
self._section_stack.update(new_section)
|
|
|
self._current_section_name = section_name
|
|
|
except ParseException:
|
|
|
return
|
|
|
|
|
|
def _parse_section_end_line(self, line):
|
|
|
try:
|
|
|
self._section_end_parser.parseString(line)
|
|
|
self._match = True
|
|
|
|
|
|
last_section_name, last_section = self._section_stack.popitem()
|
|
|
|
|
|
if self._section_stack == OrderedDict():
|
|
|
self._item_to_add = OrderedDict({last_section_name:
|
|
|
last_section})
|
|
|
self._ready_to_update = True
|
|
|
self._current_section_name = ''
|
|
|
else:
|
|
|
self._current_section_name = next(reversed(
|
|
|
self._section_stack
|
|
|
))
|
|
|
except ParseException:
|
|
|
return
|
|
|
|
|
|
def _parse_parameter_line(self, line):
|
|
|
try:
|
|
|
parsing_result = self._parameter_line_parser.parseString(line)
|
|
|
self._match = True
|
|
|
|
|
|
parameter_name = tuple(parsing_result.name.asList())
|
|
|
parameter_value = (self._last_comments_list
|
|
|
+ [parsing_result.value.strip()])
|
|
|
self._last_comments_list = []
|
|
|
parameter = OrderedDict({parameter_name: parameter_value})
|
|
|
|
|
|
if self._section_stack == OrderedDict():
|
|
|
self._item_to_add = parameter
|
|
|
self._ready_to_update = True
|
|
|
else:
|
|
|
self._section_stack[self._current_section_name].update(
|
|
|
parameter
|
|
|
)
|
|
|
except ParseException:
|
|
|
return
|
|
|
|
|
|
def _parse_parameter_to_delete_line(self, line):
|
|
|
try:
|
|
|
parsing_result = self._parameter_to_delete_parser.parseString(line)
|
|
|
self._match = True
|
|
|
|
|
|
parameter_name = tuple(parsing_result.asList())
|
|
|
parameter_value = self._last_comments_list + ['']
|
|
|
self._last_comments_list = []
|
|
|
parameter = OrderedDict({parameter_name: parameter_value})
|
|
|
|
|
|
if self._section_stack == OrderedDict():
|
|
|
self._item_to_add = parameter
|
|
|
self._ready_to_update = True
|
|
|
else:
|
|
|
self._section_stack[self._current_section_name].update(
|
|
|
parameter
|
|
|
)
|
|
|
except ParseException:
|
|
|
return
|
|
|
|
|
|
def _parse_include_line(self, line):
|
|
|
try:
|
|
|
parsing_result = self._include_line_parser.parseString(line)
|
|
|
self._match = True
|
|
|
|
|
|
parameter_name = tuple(parsing_result.asList())
|
|
|
|
|
|
if parsing_result.action == '-':
|
|
|
return
|
|
|
|
|
|
parameter_value = self._last_comments_list + ['']
|
|
|
self._last_comments_list = []
|
|
|
include_item = OrderedDict({parameter_name: parameter_value})
|
|
|
|
|
|
if self._section_stack == OrderedDict():
|
|
|
self._item_to_add = include_item
|
|
|
self._ready_to_update = True
|
|
|
else:
|
|
|
self._section_stack[self._current_section_name].update(
|
|
|
include_item
|
|
|
)
|
|
|
except ParseException:
|
|
|
return
|
|
|
|
|
|
def _parse_comment_line(self, line):
|
|
|
try:
|
|
|
parsing_result = self._comment_line_parser.parseString(line)
|
|
|
self._match = True
|
|
|
|
|
|
if not self._ignore_comments:
|
|
|
self._last_comments_list.append(parsing_result.comment)
|
|
|
except ParseException:
|
|
|
return
|