Files
WoWDBDefs/code/Python/dbd.py

281 lines
8.9 KiB
Python
Executable File

#! /usr/bin/env python
from modgrammar import *
from itertools import chain
import os
import codecs
# 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 (single build), build_version_raw[2] (build range)
# .major: int
# .minor : int
# .patch : int
# .build : int
# .layouts[]: string
# .comments[]: string
# .entries[]:
# .column: string
# .int_width: int
# .is_unsigned: bool
# .array_size: int
# .annotation: string
# .comment: string
def parse_dbd(content):
return dbd_parser.parse_string(content)
def parse_dbd_file(path):
try:
with codecs.open(path, encoding=u'utf-8') as f:
return parse_dbd(f.read())
except Exception as ex:
raise Exception(u'failed to parse dbd file "{}": {}'.format (path, ex))
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)
def stru(elem):
return u'{}'.format(elem)
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 = stru(self.elements[5]).strip() if self.elements[5] else None
def __str__(self):
return u"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)
def __le__(self, rhs):
return (self.major, self.minor, self.patch, self.build) \
<= (rhs.major, rhs.minor, rhs.patch, rhs.build)
def __eq__(self, rhs):
return (self.major, self.minor, self.patch, self.build) \
== (rhs.major, rhs.minor, rhs.patch, rhs.build)
def __ne__(self, rhs):
return (self.major, self.minor, self.patch, self.build) \
!= (rhs.major, rhs.minor, rhs.patch, rhs.build)
def __gt__(self, rhs):
return (self.major, self.minor, self.patch, self.build) \
> (rhs.major, rhs.minor, rhs.patch, rhs.build)
def __ge__(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 None
self.builds = (build_version_raw(lhs.major, lhs.minor, lhs.patch, lhs.build),
build_version_raw(rhs.major, rhs.minor, rhs.patch, rhs.build)) if rhs else lhs
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))),
G(identifier, name="column_name"),
OPTIONAL(G(L("<"), G(OPTIONAL(L("u")), 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.column = str(self.elements[1]) if self.elements[1] else None
self.is_unsigned = False
self.int_width = None
if self.elements[2]:
int_width = str(self.elements[2])
if int_width[0:1] == u"u":
int_width = int_width[1:]
self.is_unsigned = True
self.int_width = int(int_width)
self.array_size = int(str(self.elements[3])) if self.elements[3] else None
self.comment = stru(self.elements[4]).strip() if self.elements[4] else None
def __str__(self):
return u"column={} int_width={} array_size={} annotation={} comment={}".format(self.column, self.int_width, self.array_size, self.annotation, self.comment)
grammar_tags = ["ENTRY"]
class definitions(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, rec_depth=0):
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
self.builds = list(chain.from_iterable(([builds.builds for builds in self.find_all("BUILD")])))
# 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 = [stru(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,
ZERO_OR_MORE(column_definition, EOL),
ZERO_OR_MORE(ONE_OR_MORE(EOL), definitions),
ZERO_OR_MORE(EOL), EOF
)
def grammar_elem_init(self, sessiondata):
self.columns = [x[0] for x in self.elements[2]]
self.definitions = [x[1] for x in self.elements[3]]
dbd_parser = dbd_file.parser()