header.py

changeset 47
4da025d0b283
child 48
38b0919c1934
--- /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))

mercurial