55 reason = self.reason, |
63 reason = self.reason, |
56 ) |
64 ) |
57 def __str__(self): |
65 def __str__(self): |
58 return reason |
66 return reason |
59 |
67 |
|
68 class HistoryEntry: |
|
69 def __init__(self, date, user, text): |
|
70 self.date, self.user, self.text = date, user, text |
|
71 def __repr__(self): |
|
72 return str.format( |
|
73 'HistoryEntry({date!r}, {user!r}, {text!r})', |
|
74 date = self.date, |
|
75 user = self.user, |
|
76 text = self.text) |
|
77 |
60 class HeaderParser: |
78 class HeaderParser: |
61 def __init__(self): |
79 def __init__(self): |
62 self.model_body = None |
80 self.model_body = None |
63 self.cursor = 0 |
81 self.cursor = 0 |
64 self.problems = [] |
82 self.problems = [] |
65 def parse(self, model_body): |
83 def parse(self, model_body): |
66 result = Header() |
84 result = Header() |
|
85 self.result = result |
67 self.order = [] |
86 self.order = [] |
68 self.cursor = -1 |
87 self.cursor = -1 |
69 self.model_body = model_body |
88 self.model_body = model_body |
70 self.skip_to_next() |
89 self.skip_to_next() |
71 result.description = self.current() |
90 result.description = self.current() |
72 self.skip_to_next() |
91 self.skip_to_next() |
73 result.name = self.parse_pattern('^Name: (.+)$', 'name')[0] |
92 result.name = self.parse_pattern('^Name: (.+)$', 'name')[0] |
74 self.skip_to_next() |
93 self.skip_to_next() |
75 result.author = self.parse_pattern('^Author: (.+)$', 'author')[0] |
94 result.author, result.username = self.parse_pattern(r'^Author: ([^\[]+)\s*(?:\[(.+)\])?$', 'author') |
76 for header_entry in self.get_more_header_stuff(): |
95 for header_entry in self.get_more_header_stuff(): |
77 if self.try_to_match( |
96 if self.try_to_match( |
78 '^!LDRAW_ORG ' + |
97 '^!LDRAW_ORG ' + |
79 '(' \ |
98 r'(' \ |
80 '(?:Unofficial_)?' \ |
99 '(?:Unofficial_)?' \ |
81 'Part|' \ |
100 'Part|' \ |
82 'Subpart|' \ |
101 'Subpart|' \ |
83 'Primitive|' \ |
102 'Primitive|' \ |
84 '8_Primitive|' \ |
103 '8_Primitive|' \ |
85 '48_Primitive|' \ |
104 '48_Primitive|' \ |
86 'Shortcut' \ |
105 'Shortcut' \ |
87 ')\s?' \ |
106 ')\s?' \ |
88 '(ORIGINAL|UPDATE \d\d\d\d-\d\d)?$', |
107 '(.*)$', |
89 'part type'): |
108 'part type'): |
90 result.filetype, result.qualifiers = self.groups |
109 result.filetype = self.groups[0] |
|
110 result.qualifiers = re.findall(r'(?:Physical_Colour|Alias|ORIGINAL|UPDATE \d\d\d\d-\d\d)', self.groups[1]) |
91 elif self.try_to_match( |
111 elif self.try_to_match( |
92 '^!LICENSE (.+)$', |
112 '^!LICENSE (.+)$', |
93 'license'): |
113 'license'): |
94 result.license = self.groups() |
114 result.license = self.groups[0] |
|
115 elif self.try_to_match( |
|
116 'BFC (CERTIFY CW|CERTIFY CCW|NOCERTIFY)', |
|
117 'bfc'): |
|
118 result.bfc = self.groups[0] |
|
119 elif self.try_to_match( |
|
120 r'!HISTORY (\d{4}-\d{2}-\d{2}) ([\[{][^\]}]+[\]}]) (.+)$', |
|
121 'history'): |
|
122 result.history.append(HistoryEntry( |
|
123 date = datetime.datetime.strptime(self.groups[0], '%Y-%m-%d').date(), |
|
124 user = self.groups[1], |
|
125 text = self.groups[2], |
|
126 )) |
|
127 elif self.try_to_match( |
|
128 r'!HELP (.+)', |
|
129 'help'): |
|
130 if result.help: |
|
131 result.help += '\n' |
|
132 result.help += self.groups[0] |
|
133 elif self.try_to_match( |
|
134 r'!CATEGORY (.+)', |
|
135 'category'): |
|
136 result.category = self.groups[0] |
|
137 elif self.try_to_match( |
|
138 r'!KEYWORDS (.+)', |
|
139 'keywords'): |
|
140 if result.keywords: |
|
141 result.keywords += '\n' |
|
142 result.keywords += self.groups[0] |
|
143 elif self.try_to_match( |
|
144 r'!CMDLINE (.+)', |
|
145 'cmdline'): |
|
146 result.cmdline = self.groups[0] |
95 else: |
147 else: |
96 self.parse_error("couldn't handle header metacommand: " + repr(header_entry.text)) |
148 self.parse_error("couldn't understand header syntax: " + repr(header_entry.text)) |
97 return { |
149 return { |
98 'header': result, |
150 'header': result, |
99 'end-index': self.cursor + 1, |
151 'end-index': self.cursor + 1, |
100 } |
152 } |
101 def parse_error(self, message): |
153 def parse_error(self, message): |
121 if isinstance(entry, linetypes.MetaCommand): |
173 if isinstance(entry, linetypes.MetaCommand): |
122 return |
174 return |
123 def try_to_match(self, pattern, patterntype): |
175 def try_to_match(self, pattern, patterntype): |
124 try: |
176 try: |
125 self.groups = self.parse_pattern(pattern, patterntype) |
177 self.groups = self.parse_pattern(pattern, patterntype) |
|
178 return True |
126 except: |
179 except: |
127 return False |
180 return False |
128 def current(self): |
181 def current(self): |
129 entry = self.model_body[self.cursor] |
182 entry = self.model_body[self.cursor] |
130 assert isinstance(entry, linetypes.MetaCommand) |
183 assert isinstance(entry, linetypes.MetaCommand) |
131 return entry.text |
184 return entry.text |
132 def parse_pattern(self, pattern, description): |
185 def parse_pattern(self, pattern, description): |
133 match = re.search(pattern, self.current()) |
186 match = re.search(pattern, self.current()) |
134 if match: |
187 if match: |
135 self.order.append(description) |
188 self.order.append(description) |
|
189 if description not in self.result.first_occurrence: |
|
190 self.result.first_occurrence[description] = self.cursor |
136 return match.groups() |
191 return match.groups() |
137 else: |
192 else: |
138 self.parse_error(str.format("couldn't parse {}", description)) |
193 self.parse_error(str.format("couldn't parse {}", description)) |