configurationengine/source/plugins/common/ConeTemplatePlugin/templatemlplugin/templatemlplugin.py
Adding EPL version of configurationengine.
#
# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:
#
'''
Template plugin for ConE that handles templateml files. Utilizes Jinja template engine.
'''
import re
import os
import sys
import logging
import codecs
import xml.parsers.expat
from jinja2 import Environment, PackageLoader, FileSystemLoader, Template, DictLoader
import traceback
try:
from cElementTree import ElementTree
except ImportError:
try:
from elementtree import ElementTree
except ImportError:
try:
from xml.etree import cElementTree as ElementTree
except ImportError:
from xml.etree import ElementTree
try:
from cElementTree import ElementInclude
except ImportError:
try:
from elementtree import ElementInclude
except ImportError:
try:
from xml.etree import cElementInclude as ElementInclude
except ImportError:
from xml.etree import ElementInclude
import __init__
from cone.public import exceptions,plugin,utils,api
from cone.confml import persistentconfml
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
class TemplatemlImpl(plugin.ImplBase):
context = None
"""
Implementation class of template plugin.
"""
IMPL_TYPE_ID = "templateml"
def __init__(self,ref,configuration, reader=None):
"""
Overloading the default constructor
"""
plugin.ImplBase.__init__(self,ref,configuration)
self.logger = logging.getLogger('cone.templateml(%s)' % self.ref)
self.errors = False
self.reader = reader
if self.reader and self.reader.tags:
self.set_tags(self.reader.tags)
def get_context(self):
if TemplatemlImpl.context == None:
TemplatemlImpl.context = self.create_dict()
return TemplatemlImpl.context
def generate(self, context=None):
"""
Generate the given implementation.
"""
self.create_output()
return
def create_output(self, layers=None):
generator = Generator(self.reader.outputs, self.reader.filters, self.get_context(), self.configuration)
generator.generate(self.output, self.ref)
return
def get_refs(self):
refs = []
for output in self.reader.outputs:
template = output.template.template
refs.extend(self._extract_refs_from_template(template))
return refs
@classmethod
def _extract_refs_from_template(cls, template_text):
refs = []
pattern = re.compile(r'feat_tree\.((?:\.?\w+)*)', re.UNICODE)
for m in re.finditer(pattern, template_text):
ref = m.group(1)
# ref may now be e.g. 'MyFeature.MySetting._value', so
# remove the last part if it starts with an underscore
index = ref.rfind('.')
if index != -1 and index < len(ref) and ref[index + 1] == '_':
ref = ref[:index]
refs.append(ref)
return refs
def has_ref(self, refs):
"""
@returns True if the implementation uses the given ref as input value.
Otherwise return False.
"""
# Does not support template inheritance
if not isinstance(refs, list):
refs = [refs]
for output in self.reader.outputs:
if re.search("feat_list.*", output.template.template) != None:
return True
refs_in_templates = self.get_refs()
for ref in refs:
if ref in refs_in_templates:
return True
return False
def list_output_files(self):
""" Return a list of output files as an array. """
result = []
for output in self.reader.outputs:
result.append(os.path.normpath(os.path.join(self.output, output.path, output.filename)))
return result
def create_dict(self):
"""
Creates dict from configuration that can be passed to template engine.
"""
context_dict = {}
if self.configuration:
dview = self.configuration.get_default_view()
feat_list = []
feat_tree = {}
def add_feature(feature, feature_dict):
fea_dict = FeatureDictProxy(feature)
feat_list.append(fea_dict)
feature_dict[feature.ref] = fea_dict
# Recursively add sub-features
for sfeat in feature.list_features():
add_feature(feature.get_feature(sfeat), fea_dict)
for fea in dview.list_features():
add_feature(dview.get_feature(fea), feat_tree)
context_dict['feat_list'] = feat_list
context_dict['feat_tree'] = feat_tree
context_dict['configuration'] = self.configuration
return context_dict
def _expand_refs(text, config):
if config is not None:
return utils.expand_refs_by_default_view(text, config.get_default_view())
else:
return text
def _read_relative_file(configuration, relative_path, file_path):
"""
Read data from a file relative to the given other file path.
"""
# Get the actual path (relative to the current file)
base_path = os.path.dirname(file_path)
tempfile_path = os.path.normpath(os.path.join(base_path, relative_path)).replace('\\', '/')
# Read the file
resource = configuration.get_resource(tempfile_path)
try: return resource.read()
finally: resource.close()
class TemplatemlImplReader(plugin.ReaderBase):
"""
Parses a single templateml file
"""
NAMESPACE = 'http://www.s60.com/xml/templateml/1'
FILE_EXTENSIONS = ['templateml']
def __init__(self, resource_ref=None, configuration=None):
self.desc = None
self.namespaces = [self.NAMESPACE]
self.outputs = None
self.filters = None
self.tags = None
self.resource_ref = resource_ref
self.configuration = configuration
@classmethod
def read_impl(cls, resource_ref, configuration, etree):
reader = TemplatemlImplReader(resource_ref, configuration)
reader.from_elementtree(etree)
return TemplatemlImpl(resource_ref, configuration, reader)
def fromstring(self, xml_string):
etree = ElementTree.fromstring(xml_string)
self.from_elementtree(etree)
def from_elementtree(self, etree):
ElementInclude.include(etree)
self.desc = self.parse_desc(etree)
self.outputs = self.parse_outputs(etree)
self.filters = self.parse_filters(etree)
self.tags = self.parse_tags(etree)
def parse_desc(self,etree):
desc = ""
desc_elem = etree.find("{%s}desc" % self.namespaces[0])
if desc_elem != None:
desc = desc_elem.text
return desc
def parse_filters(self, etree):
filters = []
filter_elems = etree.findall("{%s}filter" % self.namespaces[0])
for filter_elem in filter_elems:
if filter_elem != None:
filter = Filter()
if filter_elem.get('name') != None:
name = filter_elem.get('name')
if self.configuration != None:
name = utils.expand_refs_by_default_view(name, self.configuration.get_default_view())
filter.set_name(name)
if filter_elem.get('file') != None:
file = filter_elem.get('file')
if self.configuration != None:
file = utils.expand_refs_by_default_view(file, self.configuration.get_default_view())
filter.set_path(file)
if filter_elem.text != None:
filter.set_code(filter_elem.text)
if filter_elem.get('file') != None:
logging.getLogger('cone.templateml').warning("In filter element file attribute and text defined. Using filter found from file attribute.")
filters.append(filter)
return filters
def parse_tags(self, etree):
tags = {}
for tag in etree.getiterator("{%s}tag" % self.namespaces[0]):
tagname = tag.get('name','')
tagvalue = tag.get('value')
values = tags.get(tagname,[])
values.append(tagvalue)
tags[tagname] = values
return tags
def parse_template(self, output_elem):
tempfile = TempFile()
template_elems = output_elem.findall("{%s}template" % self.namespaces[0])
for template_elem in template_elems:
if template_elem.text != None:
tempfile.set_template(template_elem.text)
else:
for selem in template_elem:
tempfile.set_template(selem.text)
if template_elem.get('file') != None:
file = template_elem.get('file')
if template_elem.text != None:
logging.getLogger('cone.templateml').warning("In template element file attribute and text defined. Using template found from file attribute.")
template_text = _read_relative_file(self.configuration, file, self.resource_ref)
tempfile.set_template(template_text)
return tempfile
def parse_outputs(self, etree):
outputs = []
output_elems = etree.findall("{%s}output" % self.namespaces[0])
for output_elem in output_elems:
if output_elem != None:
outputfile = OutputFile()
if output_elem.get('encoding') != None:
encoding = output_elem.get('encoding')
# Check the encoding
try:
codecs.lookup(encoding)
except LookupError:
raise exceptions.ParseError("Invalid output encoding: %s" % encoding)
if self.configuration != None:
encoding = utils.expand_refs_by_default_view(encoding, self.configuration.get_default_view())
outputfile.set_encoding(encoding)
if output_elem.get('file') != None:
file = output_elem.get('file')
if self.configuration != None:
file = utils.expand_refs_by_default_view(file, self.configuration.get_default_view())
outputfile.set_filename(file)
if output_elem.get('dir') != None:
dir = output_elem.get('dir')
if self.configuration != None:
dir = utils.expand_refs_by_default_view(dir, self.configuration.get_default_view())
outputfile.set_path(dir)
if output_elem.get('ref'):
# Fetch the output value from a configuration reference
fea = self.configuration.get_default_view().get_feature(output_elem.get('ref'))
outputfile.set_filename(fea.value)
if output_elem.get('bom'):
outputfile.bom = output_elem.get('bom').lower() in ('1', 'true', 't', 'yes', 'y')
outputfile.set_template(self.parse_template(output_elem))
outputfile.set_filters(self.parse_filters(output_elem))
outputs.append(outputfile)
return outputs
class Generator(object):
"""
Class that generates
"""
def __init__(self, outputs, filters, context, configuration=None):
self.outputs = outputs
self.filters = filters
self.context = context
self.configuration = configuration
def generate(self, output_path, ref):
"""
Generates output based on templates
"""
if self.outputs != None:
for output in self.outputs:
try:
logging.getLogger('cone.templateml').debug(output)
out_path = os.path.abspath(os.path.join(output_path, output.path))
if out_path != '':
if not os.path.exists(out_path):
os.makedirs(out_path)
out_file = open(os.path.join(out_path, output.filename), 'wb')
if output.template.path:
output.template.template = _read_relative_file(self.configuration, output.template.path, ref)
dict_loader = DictLoader({'template': output.template.template})
env = Environment(loader=dict_loader)
# Common filters
for filter in self.filters:
if filter.path:
filter.code = _read_relative_file(self.configuration, filter.path, ref)
if not filter.code:
logging.getLogger('cone.templateml').warning("Skipping empty filter definition.")
else:
env.filters[str(filter.name)] = eval(filter.code)
# Output file specific filters
for filter in output.filters:
if filter.path:
filter.code = _read_relative_file(self.configuration, filter.path, ref)
if not filter.code:
logging.getLogger('cone.templateml').warning("Skipping empty filter definition.")
else:
env.filters[str(filter.name)] = eval(filter.code)
template = env.get_template('template')
file_string = template.render(self.context)
out_file.write(self._encode_data(file_string, output.encoding, output.bom))
out_file.close()
except Exception, e:
logging.getLogger('cone.templateml').error('Failed to generate template: %s %s\n%s' % (type(e), e, traceback.format_exc()) )
else:
logging.getLogger('cone.templateml').info('No (valid) templates found.')
def _encode_data(self, data, encoding, write_bom):
"""
Encode the given data using the given encoding and BOM definition.
@param data: The data to encode.
@param encoding: The encoding to use.
@param write_bom: True or False to define whether the BOM should be written
for Unicode encodings, None for default.
"""
data = data.encode(encoding)
# Check if we need to do special handling for BOM
if write_bom is not None:
BOM_MAPPING = {'utf-8' : codecs.BOM_UTF8,
'utf-16' : codecs.BOM_UTF16,
'utf-16-be' : codecs.BOM_UTF16_BE,
'utf-16-le' : codecs.BOM_UTF16_LE}
# Use the name from a CodecInfo object to account for
# aliases (e.g. U8 and UTF-8 both map to utf-8)
codec_info = codecs.lookup(encoding)
if codec_info.name in BOM_MAPPING:
# Add or remove as necessary
BOM = BOM_MAPPING[codec_info.name]
if write_bom == True and not data.startswith(BOM):
data = BOM + data
elif write_bom == False and data.startswith(BOM):
data = data[len(BOM):]
return data
class OutputFile(object):
def __init__(self):
self.filename = ''
self.path = ''
self.encoding = "utf-8"
self.template = TempFile()
self.filters = []
self.bom = None
def set_filename(self, filename):
self.filename = filename
def set_path(self, path):
self.path = path
def set_encoding(self, encoding):
self.encoding = encoding
def set_template(self, template):
self.template = template
def add_filter(self, filter):
self.filters.append(filters)
def set_filters(self, filters):
self.filters = filters
def __eq__(self, other):
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):
return True
return False
def __repr__(self):
return "OutputFile(filename=%r, path=%r, encoding=%r, template=%r, filters=%r" % (self.filename, self.path, self.encoding, self.template, self.filters)
class TempFile(object):
def __init__(self):
self.template = ""
self.extensions = []
self.filters = []
self.path = ''
def set_path(self, path):
self.path = path
def set_template(self, template):
self.template = template
def add_extension(self, extension):
self.extensions.append(extension)
def add_filter(self, filter):
self.filters.append(filter)
def add_filter2(self, name, code):
self.filters.append(Filter(name, code))
def __eq__(self, other):
if self.template == other.template and self.filters == other.filters and self.extensions == other.extensions and self.path == other.path:
return True
return False
class Filter(object):
def __init__(self, name, code):
self.name = name
self.code = code
self.path = None
def __init__(self):
self.name = None
self.code = None
self.path = None
def set_path(self, path):
self.path = path
def set_name(self, name):
self.name = name
def set_code(self, code):
self.code = code
def __eq__(self, other):
if self.name == other.name and self.code == other.code and self.path == other.path:
return True
return False
class FeatureDictProxy(object):
"""
Proxy class that behaves like a dictionary, but loads attributes from
the Feature object it proxies only when they are requested.
"""
def __init__(self, feature):
self._feature = feature
self._children = {}
def _get_dict(self):
result = {
'_name' : self._feature.name,
'_namespace' : self._feature.namespace,
'_value' : self._feature.get_value(),
'_fqr' : self._feature.fqr,
'_type' : self._feature.type}
for ref, obj in self._children.iteritems():
result[ref] = obj
return result
def items(self):
return self._get_dict().items()
def iteritems(self):
return self._get_dict().iteritems()
def __getitem__(self, name):
if name == '_name': return self._feature.name
elif name == '_namespace': return self._feature.namespace
elif name == '_value': return self._feature.get_value()
elif name == '_fqr': return self._feature.fqr
elif name == '_type': return self._feature.type
else: return self._children[name]
def __setitem__(self, name, value):
self._children[name] = value
def __len__(self):
return len(self._get_dict())
def __eq__(self, other):
if not isinstance(other, dict):
return False
else:
return self._get_dict() == other
def __ne__(self, other):
return not (self == other)