Source code for zensols.db.parse
"""Simple parsing of a dynamic definition langauge (DDL) and dynamic
manipulation language (DML) files.
"""
__author__ = 'Paul Landes'
from typing import Tuple, Dict, List, Iterable, ClassVar
from dataclasses import dataclass, field
import logging
import re
import itertools as it
from pathlib import Path
from zensols.persist import persisted
logger = logging.getLogger(__name__)
[docs]
@dataclass
class DynamicDataParser(object):
"""Parse a DDL/DML file meant also for prototyping.
For example the file::
-- meta=init_sections=create_tables,create_idx
-- name=create_idx
create index person_name on person(name);
-- name=create_tables
create table person (id int, name text, age int);
Would have ``create_idx`` and ``create_tables`` as sections and meta data::
{'init_sections':
'create_tables,create_idx'}
"""
COMMENT_PAT: ClassVar[re.Pattern] = re.compile(r'^--.*')
SEC_START_PAT: ClassVar[re.Pattern] = re.compile(
r'^-- name=([a-zA-Z0-9_]+)')
META_PAT: ClassVar[re.Pattern] = re.compile(
r'^-- meta=([a-zA-Z0-9_]+)=(.+)$')
dd_path: Path = field()
"""The path of the file to parse."""
def _map_section_content(self, lines: List[str]) -> str:
return '\n'.join(lines)
@persisted('__parse')
def _parse(self) -> Tuple[Dict[str, str], Dict[str, str]]:
if logger.isEnabledFor(logging.INFO):
logger.info(f'parsing {self.dd_path}')
secs: List[str, Tuple[str, List[str]]] = []
sec_content: List[str] = []
meta: Dict[str, str] = {}
with open(self.dd_path) as f:
line: str
for line in f.readlines():
line = line.rstrip()
if len(line) == 0:
continue
if re.match(self.COMMENT_PAT, line):
if logger.isEnabledFor(logging.DEBUG):
logger.debug(f'matched comment: {line}')
sec_start = re.match(self.SEC_START_PAT, line)
meta_match = re.match(self.META_PAT, line)
sec_content = []
if sec_start is not None:
name = sec_start.group(1)
secs.append((name, sec_content))
elif meta_match is not None:
meta[meta_match.group(1)] = meta_match.group(2)
else:
sec_content.append(line)
sections = {x[0]: self._map_section_content(x[1]) for x in secs}
return sections, meta
@property
def sections(self) -> Dict[str, str]:
"""Return the sections of the file.
"""
return self._parse()[0]
@property
def metadata(self) -> Dict[str, str]:
"""Return the meta data found int he parse object.
"""
return self._parse()[1]
[docs]
def get_init_db_sqls(self) -> Iterable[str]:
"""Return the set of statements that create all DB objects needed to
fully CRUD.
"""
init_secs = self.metadata['init_sections']
secs = init_secs.split(',')
entries = map(lambda x: self.sections[x], secs)
sts = map(lambda x: re.split(';[ \t\n]*', x, flags=re.MULTILINE),
entries)
return filter(lambda x: len(x) > 0, it.chain(*sts))