added work on header check

Sat, 25 May 2019 09:41:33 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sat, 25 May 2019 09:41:33 +0200
changeset 47
4da025d0b283
parent 39
abc83875167e
child 48
38b0919c1934

added work on header check

header.py file | annotate | diff | comparison | revisions
ldcheck.py file | annotate | diff | comparison | revisions
parse.py file | annotate | diff | comparison | revisions
tests/misc.py file | annotate | diff | comparison | revisions
testsuite.py file | annotate | diff | comparison | revisions
--- /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))
--- a/ldcheck.py	Fri May 24 17:44:04 2019 +0200
+++ b/ldcheck.py	Sat May 25 09:41:33 2019 +0200
@@ -8,6 +8,7 @@
 from geometry import *
 from pathlib import Path
 import linetypes
+import header
 
 from os.path import realpath
 script_directory = Path(realpath(__file__)).parent
@@ -29,7 +30,18 @@
         parse_ldraw_code(line)
         for line in file
     ]
-    model = Model(body = model_body)
+    headerparser = header.HeaderParser()
+    try:
+        header_parse_result = headerparser.parse(model_body)
+        header_object = header_parse_result['header']
+        end = header_parse_result['end-index']
+    except header.HeaderError as error:
+        header_object = header.BadHeader(error.index, error.reason)
+        end = 0
+    model = Model(
+        header = header_object,
+        body = model_body,
+        header_size = end)
     model.name = name
     return model
 
@@ -71,9 +83,10 @@
         ]
 
 class Model:
-    def __init__(self, body):
+    def __init__(self, header, body, *, header_size = 0):
+        self.header = header
         self.body = body
-        self.body_offset = 0
+        self.header_size = header_size
     def filter_by_type(self, type):
         yield from [
             element
@@ -92,6 +105,9 @@
     @property
     def quadrilaterals(self):
         yield from self.filter_by_type(linetypes.Quadrilateral)
+    @property
+    def has_header(self):
+        return self.header and not isinstance(self.header, header.BadHeader)
 
 import argparse
 class ListTestSuiteAction(argparse.Action):
@@ -131,6 +147,10 @@
             config = config,
         )
         if args.dump_structure:
+            print('header: ' + type(model.header).__name__)
+            for key in sorted(dir(model.header)):
+                if not key.startswith('__'):
+                    print('\t' + key + ': ' + repr(getattr(model.header, key)))
             for entry in model.body:
                 print(entry)
         elif args.rebuild:
--- a/parse.py	Fri May 24 17:44:04 2019 +0200
+++ b/parse.py	Sat May 25 09:41:33 2019 +0200
@@ -2,6 +2,7 @@
 import re
 from geometry import *
 from colours import Colour
+from testsuite import error
 
 class BadLdrawLine(Exception):
     pass
--- a/tests/misc.py	Fri May 24 17:44:04 2019 +0200
+++ b/tests/misc.py	Sat May 25 09:41:33 2019 +0200
@@ -16,10 +16,20 @@
         if isinstance(element, linetypes.Error)
     )
 
+def bad_header(model):
+    import header
+    if isinstance(model.header, header.BadHeader):
+        yield error(
+            model.body[model.header.index],
+            'bad-header',
+            reason = model.header.reason,
+        )
+
 manifest = {
     'tests': {
         'colour-validity': colours_test,
         'syntax-errors': syntax_errors,
+        'header-validity': bad_header,
     },
     'messages': {
         'bad-colour': lambda colour_index: str.format(
@@ -30,5 +40,9 @@
             'syntax error: {}',
             reason,
         ),
+        'bad-header': lambda reason: str.format(
+            'bad header: {}',
+            reason,
+        ),
     },
 }
--- a/testsuite.py	Fri May 24 17:44:04 2019 +0200
+++ b/testsuite.py	Sat May 25 09:41:33 2019 +0200
@@ -85,7 +85,7 @@
         test_suite = load_tests()
     problems = []
     line_numbers = {
-        element: (i, i + 1 + model.body_offset)
+        element: (i, i + 1)
         for i, element in enumerate(model.body)
     }
     for test_name, test_function in test_suite['tests'].items():

mercurial