Sat, 25 May 2019 09:41:33 +0200
added work on header check
47 | 1 | import re |
2 | import linetypes | |
3 | ||
4 | class Header: | |
5 | def __init__(self): | |
6 | self.description = None | |
7 | self.name = None | |
8 | self.author = None | |
9 | self.username = None | |
10 | self.filetype = None | |
11 | self.qualifiers = None | |
12 | self.license = None | |
13 | self.help = [] | |
14 | self.bfc = None | |
15 | self.category = None | |
16 | self.keywords = [] | |
17 | self.cmdline = None | |
18 | self.history = [] | |
19 | ||
20 | class BadHeader: | |
21 | def __init__(self, index, reason): | |
22 | self.index = index | |
23 | self.reason = reason | |
24 | def __repr__(self): | |
25 | return str.format( | |
26 | 'header.BadHeader(index = {index!r}, reason = {reason!r})', | |
27 | index = self.index, | |
28 | reason = self.reason, | |
29 | ) | |
30 | ||
31 | geometrical_types = [ | |
32 | linetypes.LineSegment, | |
33 | linetypes.Triangle, | |
34 | linetypes.Quadrilateral, | |
35 | linetypes.ConditionalLine, | |
36 | ] | |
37 | ||
38 | def is_suitable_header_object(entry): | |
39 | return not any( | |
40 | isinstance(entry, linetype) | |
41 | for linetype in [ | |
42 | *geometrical_types, | |
43 | linetypes.Comment, | |
44 | linetypes.Error, | |
45 | ] | |
46 | ) | |
47 | ||
48 | class HeaderError(Exception): | |
49 | def __init__(self, index, reason): | |
50 | self.index, self.reason = index, reason | |
51 | def __repr__(self): | |
52 | return str.format( | |
53 | 'HeaderError({index!r}, {reason!r})', | |
54 | index = self.index, | |
55 | reason = self.reason, | |
56 | ) | |
57 | def __str__(self): | |
58 | return reason | |
59 | ||
60 | class HeaderParser: | |
61 | def __init__(self): | |
62 | self.model_body = None | |
63 | self.cursor = 0 | |
64 | self.problems = [] | |
65 | def parse(self, model_body): | |
66 | result = Header() | |
67 | self.order = [] | |
68 | self.cursor = -1 | |
69 | self.model_body = model_body | |
70 | self.skip_to_next() | |
71 | result.description = self.current() | |
72 | self.skip_to_next() | |
73 | result.name = self.parse_pattern('^Name: (.+)$', 'name')[0] | |
74 | self.skip_to_next() | |
75 | result.author = self.parse_pattern('^Author: (.+)$', 'author')[0] | |
76 | for header_entry in self.get_more_header_stuff(): | |
77 | if self.try_to_match( | |
78 | '^!LDRAW_ORG ' + | |
79 | '(' \ | |
80 | '(?:Unofficial_)?' \ | |
81 | 'Part|' \ | |
82 | 'Subpart|' \ | |
83 | 'Primitive|' \ | |
84 | '8_Primitive|' \ | |
85 | '48_Primitive|' \ | |
86 | 'Shortcut' \ | |
87 | ')\s?' \ | |
88 | '(ORIGINAL|UPDATE \d\d\d\d-\d\d)?$', | |
89 | 'part type'): | |
90 | result.filetype, result.qualifiers = self.groups | |
91 | elif self.try_to_match( | |
92 | '^!LICENSE (.+)$', | |
93 | 'license'): | |
94 | result.license = self.groups() | |
95 | else: | |
96 | self.parse_error("couldn't handle header metacommand: " + repr(header_entry.text)) | |
97 | return { | |
98 | 'header': result, | |
99 | 'end-index': self.cursor + 1, | |
100 | } | |
101 | def parse_error(self, message): | |
102 | raise HeaderError(index = self.cursor, reason = message) | |
103 | def get_more_header_stuff(self): | |
104 | while True: | |
105 | self.cursor += 1 | |
106 | if self.cursor >= len(self.model_body): | |
107 | break | |
108 | entry = self.model_body[self.cursor] | |
109 | if not is_suitable_header_object(entry): | |
110 | break | |
111 | if isinstance(entry, linetypes.MetaCommand): | |
112 | yield entry | |
113 | def skip_to_next(self, *, spaces_expected = 0): | |
114 | while True: | |
115 | if self.cursor + 1 >= len(self.model_body): | |
116 | self.parse_error('stub ldraw file') | |
117 | self.cursor += 1 | |
118 | entry = self.model_body[self.cursor] | |
119 | if not is_suitable_header_object(entry): | |
120 | self.parse_error('header is incomplete') | |
121 | if isinstance(entry, linetypes.MetaCommand): | |
122 | return | |
123 | def try_to_match(self, pattern, patterntype): | |
124 | try: | |
125 | self.groups = self.parse_pattern(pattern, patterntype) | |
126 | except: | |
127 | return False | |
128 | def current(self): | |
129 | entry = self.model_body[self.cursor] | |
130 | assert isinstance(entry, linetypes.MetaCommand) | |
131 | return entry.text | |
132 | def parse_pattern(self, pattern, description): | |
133 | match = re.search(pattern, self.current()) | |
134 | if match: | |
135 | self.order.append(description) | |
136 | return match.groups() | |
137 | else: | |
138 | self.parse_error(str.format("couldn't parse {}", description)) |