1 |
commit: c55acecf0d7d58ec898a5c9bbd36086fc5ee52f6 |
2 |
Author: André Erdmann <dywi <AT> mailerd <DOT> de> |
3 |
AuthorDate: Wed Aug 28 15:51:47 2013 +0000 |
4 |
Commit: André Erdmann <dywi <AT> mailerd <DOT> de> |
5 |
CommitDate: Wed Aug 28 15:51:47 2013 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=c55acecf |
7 |
|
8 |
overlay: (partially) parse ebuild files |
9 |
|
10 |
This commit adds a parser that parses an ebuild file partially. More concrete, |
11 |
it reads $SRC_URI, which is required distmap-related operations, e.g. for |
12 |
reliably handling package file collisions due to multiple categories. |
13 |
|
14 |
--- |
15 |
roverlay/overlay/pkgdir/ebuildparser.py | 283 ++++++++++++++++++++++++++++++++ |
16 |
1 file changed, 283 insertions(+) |
17 |
|
18 |
diff --git a/roverlay/overlay/pkgdir/ebuildparser.py b/roverlay/overlay/pkgdir/ebuildparser.py |
19 |
new file mode 100644 |
20 |
index 0000000..79e6f15 |
21 |
--- /dev/null |
22 |
+++ b/roverlay/overlay/pkgdir/ebuildparser.py |
23 |
@@ -0,0 +1,283 @@ |
24 |
+# R overlay -- overlay package, package directory, "minimal" ebuild parser |
25 |
+# -*- coding: utf-8 -*- |
26 |
+# Copyright (C) 2012, 2013 André Erdmann <dywi@×××××××.de> |
27 |
+# Distributed under the terms of the GNU General Public License; |
28 |
+# either version 2 of the License, or (at your option) any later version. |
29 |
+ |
30 |
+from __future__ import print_function |
31 |
+ |
32 |
+import os |
33 |
+import shlex |
34 |
+import string |
35 |
+ |
36 |
+import roverlay.util.objects |
37 |
+import roverlay.strutil |
38 |
+ |
39 |
+ |
40 |
+STR_FORMATTER = string.Formatter() |
41 |
+VFORMAT = STR_FORMATTER.vformat |
42 |
+ |
43 |
+class ParserException ( Exception ): |
44 |
+ pass |
45 |
+# --- end of ParserException --- |
46 |
+ |
47 |
+ |
48 |
+class SrcUriEntry ( object ): |
49 |
+ |
50 |
+ def __init__ ( self, src_uri, output_file=None ): |
51 |
+ super ( SrcUriEntry, self ).__init__() |
52 |
+ self.uri = src_uri |
53 |
+ if output_file: |
54 |
+ self.have_local = True |
55 |
+ self.local_file = output_file |
56 |
+ else: |
57 |
+ self.have_local = False |
58 |
+ self.local_file = src_uri.rpartition ( '/' ) [-1] |
59 |
+ # --- end of __init__ (...) --- |
60 |
+ |
61 |
+ def __str__ ( self ): |
62 |
+ if self.have_local: |
63 |
+ return "{URI} -> {f}".format ( URI=self.uri, f=self.local_file ) |
64 |
+ else: |
65 |
+ return self.uri |
66 |
+ # --- end of __str__ (...) --- |
67 |
+ |
68 |
+# --- end of SrcUriEntry --- |
69 |
+ |
70 |
+ |
71 |
+ |
72 |
+class EbuildParser ( object ): |
73 |
+ |
74 |
+ @classmethod |
75 |
+ def from_file ( cls, filepath, vartable=None ): |
76 |
+ instance = cls ( filepath, vartable=vartable ) |
77 |
+ instance.read() |
78 |
+ return instance |
79 |
+ # --- end of from_file (...) --- |
80 |
+ |
81 |
+ def __init__ ( self, filepath, vartable=None ): |
82 |
+ super ( EbuildParser, self ).__init__() |
83 |
+ self.filepath = filepath |
84 |
+ self.vartable = vartable |
85 |
+ # --- end of __init__ (...) --- |
86 |
+ |
87 |
+ def _read_tokens ( self, breakparse=None ): |
88 |
+ with open ( self.filepath, 'rt' ) as FH: |
89 |
+ reader = shlex.shlex ( FH ) |
90 |
+ reader.whitespace_split = False |
91 |
+ reader.wordchars += ' ,./$()[]:+-@*~<>' |
92 |
+ |
93 |
+ token = reader.get_token() |
94 |
+ if breakparse is None: |
95 |
+ while token: |
96 |
+ yield token |
97 |
+ token = reader.get_token() |
98 |
+ else: |
99 |
+ while token and not breakparse ( token ): |
100 |
+ yield token |
101 |
+ token = reader.get_token() |
102 |
+ # --- end of _read_tokens (...) --- |
103 |
+ |
104 |
+ def _read_variables ( self, do_unquote=True ): |
105 |
+ # assumption: no (important) variables after the first function |
106 |
+ |
107 |
+ |
108 |
+ # read all tokens and store them in a list |
109 |
+ # this allows to look back/ahead |
110 |
+ tokens = list ( self._read_tokens ( |
111 |
+ breakparse=( lambda s: ( len(s) > 2 and s[-2:] == '()' ) ) |
112 |
+ ) ) |
113 |
+ |
114 |
+ |
115 |
+ varname = None |
116 |
+ data = dict() |
117 |
+ |
118 |
+ last_index = len ( tokens ) - 1 |
119 |
+ ignore_next = False |
120 |
+ is_bash_array = False |
121 |
+ # 0 -> no value read, 1-> have value(s), 2 -> reject token |
122 |
+ value_mode = 0 |
123 |
+ |
124 |
+ for index, token in enumerate ( tokens ): |
125 |
+ if ignore_next: |
126 |
+ ignore_next = False |
127 |
+ pass |
128 |
+ |
129 |
+ elif index < last_index and tokens [index+1] == '=': |
130 |
+ # lookahead result: token is a varname |
131 |
+ ignore_next = True |
132 |
+ is_bash_array = False |
133 |
+ value_mode = 0 |
134 |
+ varname = token |
135 |
+ data [token] = None |
136 |
+ |
137 |
+ elif value_mode == 0: |
138 |
+ if value_mode == 0 and token == '()': |
139 |
+ is_bash_array = True |
140 |
+ value_mode = 2 |
141 |
+ data [varname] = [] |
142 |
+ |
143 |
+ elif value_mode == 0 and token == '(': |
144 |
+ is_bash_array = True |
145 |
+ value_mode = 1 |
146 |
+ data [varname] = [] |
147 |
+ |
148 |
+ else: |
149 |
+ data [varname] = token |
150 |
+ value_mode = 1 |
151 |
+ |
152 |
+ elif value_mode > 1: |
153 |
+ pass |
154 |
+ |
155 |
+ elif is_bash_array: |
156 |
+ # impiles value_mode != 0 |
157 |
+ |
158 |
+ if token == ')': |
159 |
+ value_mode = 2 |
160 |
+ else: |
161 |
+ data [varname].append ( token ) |
162 |
+ |
163 |
+# else: |
164 |
+# pass |
165 |
+ |
166 |
+ |
167 |
+ if do_unquote: |
168 |
+ return { |
169 |
+ varname: ( |
170 |
+ roverlay.strutil.unquote ( value ) if isinstance ( value, str ) |
171 |
+ else list ( |
172 |
+ roverlay.strutil.unquote ( item ) for item in value |
173 |
+ ) |
174 |
+ ) |
175 |
+ for varname, value in data.items() |
176 |
+ } |
177 |
+ else: |
178 |
+ return data |
179 |
+ # --- end of _read_variables (...) --- |
180 |
+ |
181 |
+ def _get_src_uri_entries ( self, value ): |
182 |
+ assert isinstance ( value, str ) |
183 |
+ |
184 |
+ src_uri = None |
185 |
+ want_local_file = False |
186 |
+ |
187 |
+ for s in value.split(): |
188 |
+ if not s or s[-1] == '?' or s in { '(', ')' }: |
189 |
+ pass |
190 |
+ |
191 |
+ elif want_local_file: |
192 |
+ yield SrcUriEntry ( src_uri, s ) |
193 |
+ want_local_file = False |
194 |
+ src_uri = None |
195 |
+ |
196 |
+ elif s == '->': |
197 |
+ if src_uri is None: |
198 |
+ raise Exception ( |
199 |
+ "SRC_URI: arrow operator -> without preceding URI" |
200 |
+ ) |
201 |
+ else: |
202 |
+ want_local_file = True |
203 |
+ |
204 |
+ else: |
205 |
+ if src_uri is not None: |
206 |
+ yield SrcUriEntry ( src_uri ) |
207 |
+ src_uri = s |
208 |
+ |
209 |
+ # -- end for |
210 |
+ |
211 |
+ if want_local_file: |
212 |
+ raise Exception ( "SRC_URI: arrow operator -> without local file" ) |
213 |
+ |
214 |
+ elif src_uri is not None: |
215 |
+ yield SrcUriEntry ( src_uri ) |
216 |
+ # --- end of _get_src_uri_entries (...) --- |
217 |
+ |
218 |
+ @roverlay.util.objects.abstractmethod |
219 |
+ def read ( self ): |
220 |
+ pass |
221 |
+ # --- end of read (...) --- |
222 |
+ |
223 |
+# --- end of EbuildParser --- |
224 |
+ |
225 |
+ |
226 |
+class SrcUriParser ( EbuildParser ): |
227 |
+ |
228 |
+ def __init__ ( self, filepath, vartable=None ): |
229 |
+ super ( SrcUriParser, self ).__init__ ( filepath, vartable=vartable ) |
230 |
+ self.src_uri = None |
231 |
+ # --- end of __init__ (...) --- |
232 |
+ |
233 |
+ def iter_entries ( self ): |
234 |
+ if self.src_uri: |
235 |
+ for entry in self.src_uri: |
236 |
+ yield entry |
237 |
+ # --- end of _iterate (...) --- |
238 |
+ |
239 |
+ def iter_local_files ( self, ignore_unparseable=False ): |
240 |
+ def convert_chars_with_vars ( text ): |
241 |
+ mode = 0 |
242 |
+ for index, char in enumerate ( text ): |
243 |
+ |
244 |
+ if mode == 0: |
245 |
+ if char == '$': |
246 |
+ mode = 1 |
247 |
+ else: |
248 |
+ yield char |
249 |
+ |
250 |
+ elif mode == 1: |
251 |
+ if char == '{': |
252 |
+ yield char |
253 |
+ mode = 2 |
254 |
+ else: |
255 |
+ raise ParserException ( |
256 |
+ 'cannot convert variable starting at ' |
257 |
+ 'position {:d} in {}'.format ( index, text ) |
258 |
+ ) |
259 |
+ |
260 |
+ elif mode == 2 and char in { '/', }: |
261 |
+ raise ParserException ( |
262 |
+ 'unsupported char {} inside variable at ' |
263 |
+ 'position {:d} in {}'.format ( char, index, text ) |
264 |
+ ) |
265 |
+ |
266 |
+ else: |
267 |
+ yield char |
268 |
+ # --- end of convert_chars_with_vars (...) --- |
269 |
+ |
270 |
+ varstr = lambda s: VFORMAT ( |
271 |
+ ''.join ( convert_chars_with_vars ( s ) ), (), self.vartable |
272 |
+ ) |
273 |
+ |
274 |
+ if self.src_uri: |
275 |
+ for entry in self.src_uri: |
276 |
+ local_file = entry.local_file |
277 |
+ if '$' in local_file: |
278 |
+ # TODO: need vartable (dict(P->,PN->)) |
279 |
+ # |
280 |
+ if ignore_unparseable: |
281 |
+ try: |
282 |
+ yield varstr ( local_file ) |
283 |
+ except ParserException: |
284 |
+ if not ignore_unparseable: |
285 |
+ raise |
286 |
+ else: |
287 |
+ yield varstr ( local_file ) |
288 |
+ |
289 |
+ else: |
290 |
+ yield local_file |
291 |
+ # --- end of iter_local_files (...) --- |
292 |
+ |
293 |
+ def __iter__ ( self ): |
294 |
+ return self.iter_entries() |
295 |
+ # --- end of __iter__ (...) --- |
296 |
+ |
297 |
+ def read ( self ): |
298 |
+ data = self._read_variables() |
299 |
+ |
300 |
+ if 'SRC_URI' in data: |
301 |
+ self.src_uri = list ( |
302 |
+ self._get_src_uri_entries ( data ['SRC_URI'] ) |
303 |
+ ) |
304 |
+ # --- end of read (...) --- |
305 |
+ |
306 |
+# --- end of SrcUriParser --- |