# HG changeset patch # User Teemu Piippo # Date 1558770093 -7200 # Node ID 4da025d0b2836c538e8a581c5ddcfb47592191cc # Parent abc83875167eb9f2baf2edede76091a3d5542ed1 added work on header check diff -r abc83875167e -r 4da025d0b283 header.py --- /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)) diff -r abc83875167e -r 4da025d0b283 ldcheck.py --- 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: diff -r abc83875167e -r 4da025d0b283 parse.py --- 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 diff -r abc83875167e -r 4da025d0b283 tests/misc.py --- 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, + ), }, } diff -r abc83875167e -r 4da025d0b283 testsuite.py --- 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():