| # 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) |