--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/header.py Sat May 25 09:41:33 2019 +0200 @@ -0,0 +1,138 @@ +import re +import linetypes + +class Header: + def __init__(self): + self.description = None + self.name = None + self.author = None + self.username = None + self.filetype = None + self.qualifiers = None + self.license = None + self.help = [] + self.bfc = None + self.category = None + self.keywords = [] + self.cmdline = None + self.history = [] + +class BadHeader: + def __init__(self, index, reason): + self.index = index + self.reason = reason + def __repr__(self): + return str.format( + 'header.BadHeader(index = {index!r}, reason = {reason!r})', + index = self.index, + reason = self.reason, + ) + +geometrical_types = [ + linetypes.LineSegment, + linetypes.Triangle, + linetypes.Quadrilateral, + linetypes.ConditionalLine, +] + +def is_suitable_header_object(entry): + return not any( + isinstance(entry, linetype) + for linetype in [ + *geometrical_types, + linetypes.Comment, + linetypes.Error, + ] + ) + +class HeaderError(Exception): + def __init__(self, index, reason): + self.index, self.reason = index, reason + def __repr__(self): + return str.format( + 'HeaderError({index!r}, {reason!r})', + index = self.index, + reason = self.reason, + ) + def __str__(self): + return reason + +class HeaderParser: + def __init__(self): + self.model_body = None + self.cursor = 0 + self.problems = [] + def parse(self, model_body): + result = Header() + self.order = [] + self.cursor = -1 + self.model_body = model_body + self.skip_to_next() + result.description = self.current() + self.skip_to_next() + result.name = self.parse_pattern('^Name: (.+)$', 'name')[0] + self.skip_to_next() + result.author = self.parse_pattern('^Author: (.+)$', 'author')[0] + for header_entry in self.get_more_header_stuff(): + if self.try_to_match( + '^!LDRAW_ORG ' + + '(' \ + '(?:Unofficial_)?' \ + 'Part|' \ + 'Subpart|' \ + 'Primitive|' \ + '8_Primitive|' \ + '48_Primitive|' \ + 'Shortcut' \ + ')\s?' \ + '(ORIGINAL|UPDATE \d\d\d\d-\d\d)?$', + 'part type'): + result.filetype, result.qualifiers = self.groups + elif self.try_to_match( + '^!LICENSE (.+)$', + 'license'): + result.license = self.groups() + else: + self.parse_error("couldn't handle header metacommand: " + repr(header_entry.text)) + return { + 'header': result, + 'end-index': self.cursor + 1, + } + def parse_error(self, message): + raise HeaderError(index = self.cursor, reason = message) + def get_more_header_stuff(self): + while True: + self.cursor += 1 + if self.cursor >= len(self.model_body): + break + entry = self.model_body[self.cursor] + if not is_suitable_header_object(entry): + break + if isinstance(entry, linetypes.MetaCommand): + yield entry + def skip_to_next(self, *, spaces_expected = 0): + while True: + if self.cursor + 1 >= len(self.model_body): + self.parse_error('stub ldraw file') + self.cursor += 1 + entry = self.model_body[self.cursor] + if not is_suitable_header_object(entry): + self.parse_error('header is incomplete') + if isinstance(entry, linetypes.MetaCommand): + return + def try_to_match(self, pattern, patterntype): + try: + self.groups = self.parse_pattern(pattern, patterntype) + except: + return False + def current(self): + entry = self.model_body[self.cursor] + assert isinstance(entry, linetypes.MetaCommand) + return entry.text + def parse_pattern(self, pattern, description): + match = re.search(pattern, self.current()) + if match: + self.order.append(description) + return match.groups() + else: + self.parse_error(str.format("couldn't parse {}", description))