#! /usr/bin/env python from modgrammar import * import os # parses a given dbd (as string) and returns an object with # # .columns[] # .type: string # .name: string # .is_confirmed_name: bool # .foreign # .table: string # .column: string # .comment: string # .definitions[] # .builds[]: build_version_raw # .major: int # .minor : int # .patch : int # .build : int # .layouts[]: string # .comments[]: string # .entries[]: # .column: string # .int_width: int # .array_size: int # .annotation: string # .comment: string # .override_type: string def parse_dbd(content): return dbd_parser.parse_string(content) def parse_dbd_file(path): with open(path) as f: return parse_dbd(f.read()) file_suffix = ".dbd" def parse_dbd_directory(path): dbds = {} for file in os.listdir(path): if file.endswith(file_suffix): dbds[file[:-len(file_suffix)]] = parse_dbd_file(os.path.join(path,file)) return dbds class column_type (Grammar): grammar = L("uint") | L("int") | L("string") | L("locstring") | L("float") grammar_collapse = True class identifier (Grammar): grammar = WORD("A-Za-z_", restchars="A-Za-z0-9_", fullmatch=True) grammar_collapse = True class integer (Grammar): grammar = WORD("0-9", fullmatch=True) grammar_collapse = True class layout_hash (Grammar): grammar = WORD("a-fA-F0-9", min=8, max=8, fullmatch=True) grammar_collapse = True class eol_c_comment (Grammar): grammar = (OPTIONAL(SPACE, collapse_skip=True), L("//"), REST_OF_LINE) grammar_collapse = True class comma_list_separator (Grammar): grammar = (OPTIONAL(SPACE), L(","), OPTIONAL(SPACE)) grammar_noteworthy = False grammar_collapse_skip = True class foreign_identifier (Grammar): #! \todo table is not actually a identifier, but table_name? grammar = (L("<"), identifier, L("::"), identifier, L(">")) def grammar_elem_init(self, sessiondata): self.table = self.elements[1] self.column = self.elements[3] def __str__(self): return "{}::{}".format(self.table, self.column) class column_definition (Grammar): grammar = ( column_type, OPTIONAL(foreign_identifier) , SPACE , G(identifier, name="column_name"), OPTIONAL(L("?")) , OPTIONAL(eol_c_comment) ) def grammar_elem_init(self, sessiondata): self.type = str(self.elements[0]) self.foreign = self.elements[1] self.name = str(self.elements[3]) self.is_confirmed_name = not self.elements[4] self.comment = str(self.elements[5]).strip() if self.elements[5] else None def __str__(self): return "type={} fk={} name={} confirmed={} comment={}".format(self.type, self.foreign, self.name, self.is_confirmed_name, self.comment) class build_version (Grammar): grammar = (integer, L("."), integer, L("."), integer, L("."), integer) def grammar_elem_init(self, sessiondata): self.major = int(str(self.elements[0])) self.minor = int(str(self.elements[2])) self.patch = int(str(self.elements[4])) self.build = int(str(self.elements[6])) def __str__(self): return "{}.{}.{}.{}".format(self.major, self.minor, self.patch, self.build) class build_version_raw: def __init__(self, major, minor, patch, build): self.major = major self.minor = minor self.patch = patch self.build = build def __str__(self): return "{}.{}".format (self.version(), self.build) def version(self): return "{}.{}.{}".format(self.major, self.minor, self.patch) def __lt__(self, rhs): return (self.major, self.minor, self.patch, self.build) \ < (rhs.major, rhs.minor, rhs.patch, rhs.build) class build_version_range (Grammar): grammar = (build_version, OPTIONAL(L("-"), build_version)) def grammar_elem_init(self, sessiondata): lhs = self.elements[0] rhs = self.elements[1][1] if self.elements[1] else lhs if str(lhs.major) != str(rhs.major): raise Exception("version range with differing major in {} <> {}".format(lhs, rhs)) if str(lhs.minor) != str(rhs.minor): raise Exception("version range with differing minor in {} <> {}".format(lhs, rhs)) if str(lhs.patch) != str(rhs.patch): raise Exception("version range with differing patch in {} <> {}".format(lhs, rhs)) self.builds = [] for b in range(int(str(lhs.build)), int(str(rhs.build)) + 1): self.builds += [build_version_raw(lhs.major, lhs.minor, lhs.patch, b)] class definition_BUILD (Grammar): grammar = ( L("BUILD"), SPACE , LIST_OF(build_version_range, sep=comma_list_separator, collapse=True) ) def grammar_elem_init(self, sessiondata): self.builds = [ranges.builds for ranges in self.elements[2:]] grammar_tags = ["BUILD"] class definition_LAYOUT (Grammar): grammar = ( L("LAYOUT"), SPACE , LIST_OF(layout_hash, sep=comma_list_separator, collapse=True) ) def grammar_elem_init(self, sessiondata): self.layouts = [str(layout) for layout in self.elements[2:]] grammar_tags = ["LAYOUT"] class definition_COMMENT (Grammar): grammar = ( L("COMMENT"), SPACE , G(REST_OF_LINE, tags=["COMMENT"]) ) grammar_collapse = True class definition_entry (Grammar): grammar = ( OPTIONAL(G(L("$"), G(LIST_OF(G(identifier, tags=["ANNOTATION"], sep=comma_list_separator), name="annotation", collapse=True), L("$"), collapse=True))) , OPTIONAL(G(L('#'), column_type, L('#'), collapse=True)) , G(identifier, name="column_name") , OPTIONAL(G(L("<"), G(integer, name="int_width"), L(">"), collapse=True)) , OPTIONAL(G(L("["), G(integer, name="array_size"), L("]"), collapse=True)) , OPTIONAL(eol_c_comment) ) def grammar_elem_init(self, sessiondata): self.annotation = [str(e) for e in self.elements[0].find_all("ANNOTATION")] if self.elements[0] else [] self.override_type = str(self.elements[1]) if self.elements[1] else None self.column = str(self.elements[2]) if self.elements[2] else None self.int_width = int(str(self.elements[3])) if self.elements[3] else None self.array_size = int(str(self.elements[4])) if self.elements[4] else None self.comment = str(self.elements[5]).strip() if self.elements[5] else None def __str__(self): return "column={} override_type={} int_width={} array_size={} annotation={} comment={}".format(self.column, self.override_type, self.int_width, self.array_size, self.annotation, self.comment) grammar_tags = ["ENTRY"] class definition (Grammar): grammar = ( ONE_OR_MORE(G(definition_BUILD | definition_LAYOUT | definition_COMMENT, EOL, name="definition_header")) , REPEAT(definition_entry, EOL) ) def grammar_elem_init(self, sessiondata): def flatten(lis): from collections import Iterable for item in lis: if isinstance(item, Iterable) and not isinstance(item, str): for x in flatten(item): yield x else: yield item import itertools self.builds = list(flatten([builds.builds for builds in self.find_all("BUILD")])) self.layouts = list(flatten([layouts.layouts for layouts in self.find_all("LAYOUT")])) self.comments = [str(comment) for comment in self.find_all("COMMENT")] self.entries = [entry for entry in self.find_all("ENTRY")] class dbd_file (Grammar): grammar = ( L("COLUMNS"), EOL , REPEAT(column_definition, EOL) , EOL , REPEAT(definition, EOF | EOL) ) def grammar_elem_init(self, sessiondata): self.columns = [x[0] for x in self.elements[2]] self.definitions = [x[0] for x in self.elements[4]] dbd_parser = dbd_file.parser()