configurationengine/source/plugins/common/ConeTemplatePlugin/templatemlplugin/templatemlplugin.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
equal deleted inserted replaced
-1:000000000000 0:2e8eeb919028
       
     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)