|
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)) |