|
1 # |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # This component and the accompanying materials are made available |
|
5 # under the terms of "Eclipse Public License v1.0" |
|
6 # which accompanies this distribution, and is available |
|
7 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 # |
|
9 # Initial Contributors: |
|
10 # Nokia Corporation - initial contribution. |
|
11 # |
|
12 # Contributors: |
|
13 # |
|
14 # Description: |
|
15 # |
|
16 ''' |
|
17 Template plugin for ConE that handles templateml files. Utilizes Jinja template engine. |
|
18 ''' |
|
19 |
|
20 import re |
|
21 import os |
|
22 import sys |
|
23 import logging |
|
24 import codecs |
|
25 import xml.parsers.expat |
|
26 from jinja2 import Environment, PackageLoader, FileSystemLoader, Template, DictLoader |
|
27 import traceback |
|
28 try: |
|
29 from cElementTree import ElementTree |
|
30 except ImportError: |
|
31 try: |
|
32 from elementtree import ElementTree |
|
33 except ImportError: |
|
34 try: |
|
35 from xml.etree import cElementTree as ElementTree |
|
36 except ImportError: |
|
37 from xml.etree import ElementTree |
|
38 |
|
39 try: |
|
40 from cElementTree import ElementInclude |
|
41 except ImportError: |
|
42 try: |
|
43 from elementtree import ElementInclude |
|
44 except ImportError: |
|
45 try: |
|
46 from xml.etree import cElementInclude as ElementInclude |
|
47 except ImportError: |
|
48 from xml.etree import ElementInclude |
|
49 |
|
50 import __init__ |
|
51 |
|
52 from cone.public import exceptions,plugin,utils,api |
|
53 from cone.confml import persistentconfml |
|
54 |
|
55 ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) |
|
56 |
|
57 class TemplatemlImpl(plugin.ImplBase): |
|
58 |
|
59 context = None |
|
60 |
|
61 """ |
|
62 Implementation class of template plugin. |
|
63 """ |
|
64 |
|
65 IMPL_TYPE_ID = "templateml" |
|
66 |
|
67 |
|
68 def __init__(self,ref,configuration, reader=None): |
|
69 """ |
|
70 Overloading the default constructor |
|
71 """ |
|
72 plugin.ImplBase.__init__(self,ref,configuration) |
|
73 self.logger = logging.getLogger('cone.templateml(%s)' % self.ref) |
|
74 self.errors = False |
|
75 self.reader = reader |
|
76 if self.reader and self.reader.tags: |
|
77 self.set_tags(self.reader.tags) |
|
78 |
|
79 def get_context(self): |
|
80 if TemplatemlImpl.context == None: |
|
81 TemplatemlImpl.context = self.create_dict() |
|
82 |
|
83 return TemplatemlImpl.context |
|
84 |
|
85 def generate(self, context=None): |
|
86 """ |
|
87 Generate the given implementation. |
|
88 """ |
|
89 |
|
90 self.create_output() |
|
91 return |
|
92 |
|
93 def create_output(self, layers=None): |
|
94 generator = Generator(self.reader.outputs, self.reader.filters, self.get_context(), self.configuration) |
|
95 generator.generate(self.output, self.ref) |
|
96 return |
|
97 |
|
98 def get_refs(self): |
|
99 refs = [] |
|
100 for output in self.reader.outputs: |
|
101 template = output.template.template |
|
102 refs.extend(self._extract_refs_from_template(template)) |
|
103 return refs |
|
104 |
|
105 @classmethod |
|
106 def _extract_refs_from_template(cls, template_text): |
|
107 refs = [] |
|
108 pattern = re.compile(r'feat_tree\.((?:\.?\w+)*)', re.UNICODE) |
|
109 for m in re.finditer(pattern, template_text): |
|
110 ref = m.group(1) |
|
111 |
|
112 # ref may now be e.g. 'MyFeature.MySetting._value', so |
|
113 # remove the last part if it starts with an underscore |
|
114 index = ref.rfind('.') |
|
115 if index != -1 and index < len(ref) and ref[index + 1] == '_': |
|
116 ref = ref[:index] |
|
117 |
|
118 refs.append(ref) |
|
119 return refs |
|
120 |
|
121 def has_ref(self, refs): |
|
122 """ |
|
123 @returns True if the implementation uses the given ref as input value. |
|
124 Otherwise return False. |
|
125 """ |
|
126 |
|
127 # Does not support template inheritance |
|
128 |
|
129 if not isinstance(refs, list): |
|
130 refs = [refs] |
|
131 |
|
132 for output in self.reader.outputs: |
|
133 if re.search("feat_list.*", output.template.template) != None: |
|
134 return True |
|
135 |
|
136 refs_in_templates = self.get_refs() |
|
137 |
|
138 for ref in refs: |
|
139 if ref in refs_in_templates: |
|
140 return True |
|
141 return False |
|
142 |
|
143 |
|
144 def list_output_files(self): |
|
145 """ Return a list of output files as an array. """ |
|
146 result = [] |
|
147 for output in self.reader.outputs: |
|
148 result.append(os.path.normpath(os.path.join(self.output, output.path, output.filename))) |
|
149 return result |
|
150 |
|
151 def create_dict(self): |
|
152 """ |
|
153 Creates dict from configuration that can be passed to template engine. |
|
154 """ |
|
155 |
|
156 context_dict = {} |
|
157 |
|
158 if self.configuration: |
|
159 dview = self.configuration.get_default_view() |
|
160 feat_list = [] |
|
161 feat_tree = {} |
|
162 |
|
163 def add_feature(feature, feature_dict): |
|
164 fea_dict = FeatureDictProxy(feature) |
|
165 feat_list.append(fea_dict) |
|
166 feature_dict[feature.ref] = fea_dict |
|
167 |
|
168 # Recursively add sub-features |
|
169 for sfeat in feature.list_features(): |
|
170 add_feature(feature.get_feature(sfeat), fea_dict) |
|
171 |
|
172 for fea in dview.list_features(): |
|
173 add_feature(dview.get_feature(fea), feat_tree) |
|
174 |
|
175 context_dict['feat_list'] = feat_list |
|
176 context_dict['feat_tree'] = feat_tree |
|
177 context_dict['configuration'] = self.configuration |
|
178 |
|
179 return context_dict |
|
180 |
|
181 def _expand_refs(text, config): |
|
182 if config is not None: |
|
183 return utils.expand_refs_by_default_view(text, config.get_default_view()) |
|
184 else: |
|
185 return text |
|
186 |
|
187 def _read_relative_file(configuration, relative_path, file_path): |
|
188 """ |
|
189 Read data from a file relative to the given other file path. |
|
190 """ |
|
191 # Get the actual path (relative to the current file) |
|
192 base_path = os.path.dirname(file_path) |
|
193 tempfile_path = os.path.normpath(os.path.join(base_path, relative_path)).replace('\\', '/') |
|
194 |
|
195 # Read the file |
|
196 resource = configuration.get_resource(tempfile_path) |
|
197 try: return resource.read() |
|
198 finally: resource.close() |
|
199 |
|
200 class TemplatemlImplReader(plugin.ReaderBase): |
|
201 """ |
|
202 Parses a single templateml file |
|
203 """ |
|
204 NAMESPACE = 'http://www.s60.com/xml/templateml/1' |
|
205 FILE_EXTENSIONS = ['templateml'] |
|
206 |
|
207 def __init__(self, resource_ref=None, configuration=None): |
|
208 self.desc = None |
|
209 self.namespaces = [self.NAMESPACE] |
|
210 self.outputs = None |
|
211 self.filters = None |
|
212 self.tags = None |
|
213 self.resource_ref = resource_ref |
|
214 self.configuration = configuration |
|
215 |
|
216 |
|
217 @classmethod |
|
218 def read_impl(cls, resource_ref, configuration, etree): |
|
219 reader = TemplatemlImplReader(resource_ref, configuration) |
|
220 reader.from_elementtree(etree) |
|
221 return TemplatemlImpl(resource_ref, configuration, reader) |
|
222 |
|
223 def fromstring(self, xml_string): |
|
224 etree = ElementTree.fromstring(xml_string) |
|
225 self.from_elementtree(etree) |
|
226 |
|
227 def from_elementtree(self, etree): |
|
228 ElementInclude.include(etree) |
|
229 self.desc = self.parse_desc(etree) |
|
230 self.outputs = self.parse_outputs(etree) |
|
231 self.filters = self.parse_filters(etree) |
|
232 self.tags = self.parse_tags(etree) |
|
233 |
|
234 def parse_desc(self,etree): |
|
235 desc = "" |
|
236 desc_elem = etree.find("{%s}desc" % self.namespaces[0]) |
|
237 if desc_elem != None: |
|
238 desc = desc_elem.text |
|
239 return desc |
|
240 |
|
241 def parse_filters(self, etree): |
|
242 filters = [] |
|
243 filter_elems = etree.findall("{%s}filter" % self.namespaces[0]) |
|
244 for filter_elem in filter_elems: |
|
245 if filter_elem != None: |
|
246 filter = Filter() |
|
247 |
|
248 if filter_elem.get('name') != None: |
|
249 name = filter_elem.get('name') |
|
250 if self.configuration != None: |
|
251 name = utils.expand_refs_by_default_view(name, self.configuration.get_default_view()) |
|
252 filter.set_name(name) |
|
253 if filter_elem.get('file') != None: |
|
254 file = filter_elem.get('file') |
|
255 if self.configuration != None: |
|
256 file = utils.expand_refs_by_default_view(file, self.configuration.get_default_view()) |
|
257 filter.set_path(file) |
|
258 if filter_elem.text != None: |
|
259 filter.set_code(filter_elem.text) |
|
260 if filter_elem.get('file') != None: |
|
261 logging.getLogger('cone.templateml').warning("In filter element file attribute and text defined. Using filter found from file attribute.") |
|
262 filters.append(filter) |
|
263 return filters |
|
264 |
|
265 def parse_tags(self, etree): |
|
266 tags = {} |
|
267 for tag in etree.getiterator("{%s}tag" % self.namespaces[0]): |
|
268 tagname = tag.get('name','') |
|
269 tagvalue = tag.get('value') |
|
270 values = tags.get(tagname,[]) |
|
271 values.append(tagvalue) |
|
272 tags[tagname] = values |
|
273 return tags |
|
274 |
|
275 def parse_template(self, output_elem): |
|
276 tempfile = TempFile() |
|
277 template_elems = output_elem.findall("{%s}template" % self.namespaces[0]) |
|
278 |
|
279 for template_elem in template_elems: |
|
280 if template_elem.text != None: |
|
281 tempfile.set_template(template_elem.text) |
|
282 else: |
|
283 for selem in template_elem: |
|
284 tempfile.set_template(selem.text) |
|
285 |
|
286 if template_elem.get('file') != None: |
|
287 file = template_elem.get('file') |
|
288 if template_elem.text != None: |
|
289 logging.getLogger('cone.templateml').warning("In template element file attribute and text defined. Using template found from file attribute.") |
|
290 template_text = _read_relative_file(self.configuration, file, self.resource_ref) |
|
291 tempfile.set_template(template_text) |
|
292 return tempfile |
|
293 |
|
294 def parse_outputs(self, etree): |
|
295 outputs = [] |
|
296 output_elems = etree.findall("{%s}output" % self.namespaces[0]) |
|
297 for output_elem in output_elems: |
|
298 if output_elem != None: |
|
299 outputfile = OutputFile() |
|
300 if output_elem.get('encoding') != None: |
|
301 encoding = output_elem.get('encoding') |
|
302 # Check the encoding |
|
303 try: |
|
304 codecs.lookup(encoding) |
|
305 except LookupError: |
|
306 raise exceptions.ParseError("Invalid output encoding: %s" % encoding) |
|
307 |
|
308 if self.configuration != None: |
|
309 encoding = utils.expand_refs_by_default_view(encoding, self.configuration.get_default_view()) |
|
310 outputfile.set_encoding(encoding) |
|
311 if output_elem.get('file') != None: |
|
312 file = output_elem.get('file') |
|
313 |
|
314 if self.configuration != None: |
|
315 file = utils.expand_refs_by_default_view(file, self.configuration.get_default_view()) |
|
316 outputfile.set_filename(file) |
|
317 if output_elem.get('dir') != None: |
|
318 dir = output_elem.get('dir') |
|
319 if self.configuration != None: |
|
320 dir = utils.expand_refs_by_default_view(dir, self.configuration.get_default_view()) |
|
321 outputfile.set_path(dir) |
|
322 if output_elem.get('ref'): |
|
323 # Fetch the output value from a configuration reference |
|
324 fea = self.configuration.get_default_view().get_feature(output_elem.get('ref')) |
|
325 outputfile.set_filename(fea.value) |
|
326 if output_elem.get('bom'): |
|
327 outputfile.bom = output_elem.get('bom').lower() in ('1', 'true', 't', 'yes', 'y') |
|
328 outputfile.set_template(self.parse_template(output_elem)) |
|
329 outputfile.set_filters(self.parse_filters(output_elem)) |
|
330 outputs.append(outputfile) |
|
331 return outputs |
|
332 |
|
333 class Generator(object): |
|
334 """ |
|
335 Class that generates |
|
336 """ |
|
337 |
|
338 def __init__(self, outputs, filters, context, configuration=None): |
|
339 self.outputs = outputs |
|
340 self.filters = filters |
|
341 self.context = context |
|
342 self.configuration = configuration |
|
343 |
|
344 def generate(self, output_path, ref): |
|
345 """ |
|
346 Generates output based on templates |
|
347 """ |
|
348 if self.outputs != None: |
|
349 |
|
350 for output in self.outputs: |
|
351 try: |
|
352 logging.getLogger('cone.templateml').debug(output) |
|
353 out_path = os.path.abspath(os.path.join(output_path, output.path)) |
|
354 if out_path != '': |
|
355 if not os.path.exists(out_path): |
|
356 os.makedirs(out_path) |
|
357 |
|
358 out_file = open(os.path.join(out_path, output.filename), 'wb') |
|
359 |
|
360 if output.template.path: |
|
361 output.template.template = _read_relative_file(self.configuration, output.template.path, ref) |
|
362 |
|
363 dict_loader = DictLoader({'template': output.template.template}) |
|
364 env = Environment(loader=dict_loader) |
|
365 |
|
366 # Common filters |
|
367 for filter in self.filters: |
|
368 |
|
369 if filter.path: |
|
370 filter.code = _read_relative_file(self.configuration, filter.path, ref) |
|
371 |
|
372 if not filter.code: |
|
373 logging.getLogger('cone.templateml').warning("Skipping empty filter definition.") |
|
374 else: |
|
375 env.filters[str(filter.name)] = eval(filter.code) |
|
376 |
|
377 # Output file specific filters |
|
378 for filter in output.filters: |
|
379 if filter.path: |
|
380 filter.code = _read_relative_file(self.configuration, filter.path, ref) |
|
381 |
|
382 if not filter.code: |
|
383 logging.getLogger('cone.templateml').warning("Skipping empty filter definition.") |
|
384 else: |
|
385 env.filters[str(filter.name)] = eval(filter.code) |
|
386 |
|
387 template = env.get_template('template') |
|
388 |
|
389 file_string = template.render(self.context) |
|
390 out_file.write(self._encode_data(file_string, output.encoding, output.bom)) |
|
391 out_file.close() |
|
392 |
|
393 except Exception, e: |
|
394 logging.getLogger('cone.templateml').error('Failed to generate template: %s %s\n%s' % (type(e), e, traceback.format_exc()) ) |
|
395 else: |
|
396 logging.getLogger('cone.templateml').info('No (valid) templates found.') |
|
397 |
|
398 def _encode_data(self, data, encoding, write_bom): |
|
399 """ |
|
400 Encode the given data using the given encoding and BOM definition. |
|
401 @param data: The data to encode. |
|
402 @param encoding: The encoding to use. |
|
403 @param write_bom: True or False to define whether the BOM should be written |
|
404 for Unicode encodings, None for default. |
|
405 """ |
|
406 data = data.encode(encoding) |
|
407 |
|
408 # Check if we need to do special handling for BOM |
|
409 if write_bom is not None: |
|
410 BOM_MAPPING = {'utf-8' : codecs.BOM_UTF8, |
|
411 'utf-16' : codecs.BOM_UTF16, |
|
412 'utf-16-be' : codecs.BOM_UTF16_BE, |
|
413 'utf-16-le' : codecs.BOM_UTF16_LE} |
|
414 |
|
415 # Use the name from a CodecInfo object to account for |
|
416 # aliases (e.g. U8 and UTF-8 both map to utf-8) |
|
417 codec_info = codecs.lookup(encoding) |
|
418 if codec_info.name in BOM_MAPPING: |
|
419 # Add or remove as necessary |
|
420 BOM = BOM_MAPPING[codec_info.name] |
|
421 if write_bom == True and not data.startswith(BOM): |
|
422 data = BOM + data |
|
423 elif write_bom == False and data.startswith(BOM): |
|
424 data = data[len(BOM):] |
|
425 return data |
|
426 |
|
427 |
|
428 class OutputFile(object): |
|
429 def __init__(self): |
|
430 self.filename = '' |
|
431 self.path = '' |
|
432 self.encoding = "utf-8" |
|
433 self.template = TempFile() |
|
434 self.filters = [] |
|
435 self.bom = None |
|
436 |
|
437 def set_filename(self, filename): |
|
438 self.filename = filename |
|
439 |
|
440 def set_path(self, path): |
|
441 self.path = path |
|
442 |
|
443 def set_encoding(self, encoding): |
|
444 self.encoding = encoding |
|
445 |
|
446 def set_template(self, template): |
|
447 self.template = template |
|
448 |
|
449 def add_filter(self, filter): |
|
450 self.filters.append(filters) |
|
451 |
|
452 def set_filters(self, filters): |
|
453 self.filters = filters |
|
454 |
|
455 def __eq__(self, other): |
|
456 if (self.template == other.template and self.encoding == other.encoding and self.path == other.path and self.filename == other.filename and self.filters == other.filters): |
|
457 return True |
|
458 return False |
|
459 |
|
460 def __repr__(self): |
|
461 return "OutputFile(filename=%r, path=%r, encoding=%r, template=%r, filters=%r" % (self.filename, self.path, self.encoding, self.template, self.filters) |
|
462 |
|
463 class TempFile(object): |
|
464 def __init__(self): |
|
465 self.template = "" |
|
466 self.extensions = [] |
|
467 self.filters = [] |
|
468 self.path = '' |
|
469 |
|
470 def set_path(self, path): |
|
471 self.path = path |
|
472 |
|
473 def set_template(self, template): |
|
474 self.template = template |
|
475 |
|
476 def add_extension(self, extension): |
|
477 self.extensions.append(extension) |
|
478 |
|
479 def add_filter(self, filter): |
|
480 self.filters.append(filter) |
|
481 |
|
482 def add_filter2(self, name, code): |
|
483 self.filters.append(Filter(name, code)) |
|
484 |
|
485 def __eq__(self, other): |
|
486 if self.template == other.template and self.filters == other.filters and self.extensions == other.extensions and self.path == other.path: |
|
487 return True |
|
488 return False |
|
489 |
|
490 class Filter(object): |
|
491 def __init__(self, name, code): |
|
492 self.name = name |
|
493 self.code = code |
|
494 self.path = None |
|
495 |
|
496 def __init__(self): |
|
497 self.name = None |
|
498 self.code = None |
|
499 self.path = None |
|
500 |
|
501 def set_path(self, path): |
|
502 self.path = path |
|
503 |
|
504 def set_name(self, name): |
|
505 self.name = name |
|
506 |
|
507 def set_code(self, code): |
|
508 self.code = code |
|
509 |
|
510 def __eq__(self, other): |
|
511 if self.name == other.name and self.code == other.code and self.path == other.path: |
|
512 return True |
|
513 return False |
|
514 |
|
515 class FeatureDictProxy(object): |
|
516 """ |
|
517 Proxy class that behaves like a dictionary, but loads attributes from |
|
518 the Feature object it proxies only when they are requested. |
|
519 """ |
|
520 def __init__(self, feature): |
|
521 self._feature = feature |
|
522 self._children = {} |
|
523 |
|
524 def _get_dict(self): |
|
525 result = { |
|
526 '_name' : self._feature.name, |
|
527 '_namespace' : self._feature.namespace, |
|
528 '_value' : self._feature.get_value(), |
|
529 '_fqr' : self._feature.fqr, |
|
530 '_type' : self._feature.type} |
|
531 for ref, obj in self._children.iteritems(): |
|
532 result[ref] = obj |
|
533 return result |
|
534 |
|
535 def items(self): |
|
536 return self._get_dict().items() |
|
537 |
|
538 def iteritems(self): |
|
539 return self._get_dict().iteritems() |
|
540 |
|
541 def __getitem__(self, name): |
|
542 if name == '_name': return self._feature.name |
|
543 elif name == '_namespace': return self._feature.namespace |
|
544 elif name == '_value': return self._feature.get_value() |
|
545 elif name == '_fqr': return self._feature.fqr |
|
546 elif name == '_type': return self._feature.type |
|
547 else: return self._children[name] |
|
548 |
|
549 def __setitem__(self, name, value): |
|
550 self._children[name] = value |
|
551 |
|
552 def __len__(self): |
|
553 return len(self._get_dict()) |
|
554 |
|
555 def __eq__(self, other): |
|
556 if not isinstance(other, dict): |
|
557 return False |
|
558 else: |
|
559 return self._get_dict() == other |
|
560 |
|
561 def __ne__(self, other): |
|
562 return not (self == other) |