blob: c3da715369f552e660b3b3217825293a1ebbb3b6 [file] [log] [blame]
# Copyright 2016 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A template engine based on jinja2 to use as a tool for Skylark rules."""
import gflags
import jinja2
import os
import os.path
import sys
import xml.sax.saxutils as saxutils
gflags.DEFINE_string('output', None, 'The output file, mandatory.')
gflags.MarkFlagAsRequired('output')
gflags.DEFINE_string('template', None, 'The input file, mandatory.')
gflags.MarkFlagAsRequired('template')
gflags.DEFINE_multistring(
'variable', [],
'A variable to expand in the template, in the format NAME=VALUE. Each '
'variable provided here will be available via the variables.NAME '
'variable.')
gflags.DEFINE_multistring(
'imports', [],
'A file to import as another template, in the format NAME=filename. '
'Each file imported here will be considered as template and expanded '
'as such. The order of expansion happens in the order it is given on '
'the command line, making each file available for consumption for the '
'next one. All file content will be available via the imports.NAME '
'variable.')
gflags.DEFINE_boolean(
'escape_xml', True,
'Whether to escape XML special characters in the import templates. '
'If set to True, all files specified through --imports and all values '
'specified through --variable will be escaped for XML characters '
'before inclusion in the main template.')
gflags.DEFINE_boolean(
'executable', False, 'Whether to adds the executable bit to the output.')
FLAGS = gflags.FLAGS
class OneFileLoader(jinja2.BaseLoader):
"""A file system loader that allows loading only one file."""
def __init__(self, path):
self.path = path
def get_source(self, environment, template):
if template != self.path or not os.path.exists(self.path):
raise TemplateNotFound(template)
mtime = os.path.getmtime(self.path)
source = ''
with file(self.path) as f:
source = f.read().decode('utf-8')
return source, self.path, lambda: mtime == os.path.getmtime(self.path)
def expand_template(template, variables, imports, raw_imports=None):
"""Expand a template."""
if raw_imports is None:
raw_imports = imports
env = jinja2.Environment(loader=OneFileLoader(template))
template = env.get_template(template)
return template.render(imports=imports, variables=variables, raw_imports=raw_imports)
def quote_xml(d):
"""Returns a copy of d where all values where escaped for XML."""
return {k: saxutils.escape(v) for k,v in d.iteritems()}
def construct_imports(variables, imports):
"""Construct the list of imports by expanding all command line arguments."""
result = {}
for i in imports:
kv = i.split('=', 1)
if len(kv) != 2:
print 'Invalid value for --imports: %s. See --help.' % i
sys.exit(1)
result[kv[0]] = expand_template(kv[1], variables, result)
return result
def main(flags, unused_argv):
"""Main method."""
variables = {}
for v in flags.variable:
kv = v.split('=', 1)
if len(kv) != 2:
print 'Invalid value for --variable: %s. See --help.' % v
sys.exit(-1)
variables[kv[0]] = kv[1]
imports = construct_imports(variables, flags.imports)
raw_imports = imports
if flags.escape_xml:
imports = quote_xml(imports)
variables = quote_xml(variables)
result = expand_template(flags.template, variables, imports, raw_imports)
with open(flags.output, "w") as f:
f.write(result)
if flags.executable:
os.chmod(flags.output, 0o755)
if __name__ == '__main__':
unused_argv = FLAGS(sys.argv)
main(FLAGS, unused_argv)