diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 9109b10..e9bf0f9 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -163,6 +163,37 @@ With the `suse` spec-style:: License: Apache-2.0 + +distribution specific blocks & child templates +********************************************** + +To properly handle differences between individual .spec styles, renderspec +contains child templates in `renderspec/dist-templates` which are +automatically used with corresponding `--spec-style`. These allow different +output for each spec style (distro) using jinja `{% block %}` syntax. + +For example consider simple `renderspec/dist-templates/fedora.spec.j2`: + + {% extends ".spec" %} + {% block build_requires %} + BuildRequires: {{ py2pkg('setuptools') }} + {% endblock %} + +allows following in a spec template: + + {% block build_requires %}{% endblock %} + +to render into + + BuildRequires: python-setuptools + +with `fedora` spec style, while `renderspec/dist-templates/suse.spec.j2` might +define other result for `suse` spec style. + +For more information, see current `renderspec/dist-templates` and usage in +`openstack/rpm-packaging`_ project. + + .. _Jinja2: http://jinja.pocoo.org/docs/dev/ .. _openstack/rpm-packaging: https://git.openstack.org/cgit/openstack/rpm-packaging/ .. _pymod2pkg: https://git.openstack.org/cgit/openstack/pymod2pkg diff --git a/renderspec/__init__.py b/renderspec/__init__.py index 30ab651..c7d04ed 100644 --- a/renderspec/__init__.py +++ b/renderspec/__init__.py @@ -24,11 +24,11 @@ import sys from jinja2 import contextfilter from jinja2 import contextfunction from jinja2 import Environment -from jinja2 import FileSystemLoader import pymod2pkg import yaml +from renderspec.distloader import RenderspecLoader from renderspec import versions @@ -147,12 +147,17 @@ def _env_register_filters_and_globals(env): def generate_spec(spec_style, epochs, requirements, input_template_path): """generate a spec file with the given style and the given template""" - env = Environment(loader=FileSystemLoader( - os.path.dirname(input_template_path))) + + env = Environment(loader=RenderspecLoader( + template_fn=input_template_path), + trim_blocks=True) _env_register_filters_and_globals(env) - template = env.get_template(os.path.basename(input_template_path)) + template_name = '.spec' + if spec_style in env.loader.list_templates(): + template_name = spec_style + template = env.get_template(template_name) return template.render(spec_style=spec_style, epochs=epochs, requirements=requirements) diff --git a/renderspec/dist-templates/fedora.spec.j2 b/renderspec/dist-templates/fedora.spec.j2 new file mode 100644 index 0000000..78e6a14 --- /dev/null +++ b/renderspec/dist-templates/fedora.spec.j2 @@ -0,0 +1,4 @@ +{% extends ".spec" %} +{% block build_requires %} +BuildRequires: {{ py2pkg('setuptools') }} +{% endblock %} diff --git a/renderspec/dist-templates/suse.spec.j2 b/renderspec/dist-templates/suse.spec.j2 new file mode 100644 index 0000000..3c0ff45 --- /dev/null +++ b/renderspec/dist-templates/suse.spec.j2 @@ -0,0 +1,4 @@ +{% extends ".spec" %} +{% block build_requires %} +BuildRequires: openstack-macros +{% endblock %} diff --git a/renderspec/distloader.py b/renderspec/distloader.py new file mode 100644 index 0000000..777d73d --- /dev/null +++ b/renderspec/distloader.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +# Copyright (c) 2016 Red Hat +# +# 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. + +import jinja2 +from jinja2.loaders import TemplateNotFound +from jinja2.utils import open_if_exists +import os + + +def get_dist_templates_path(): + return os.path.join(os.path.dirname(__file__), 'dist-templates') + + +class RenderspecLoader(jinja2.BaseLoader): + """A special template loader which allows rendering supplied .spec template + with distro specific blocks maintained as part of renderspec. + + '.spec' returns the spec template (which you need to supply during init) + while other strings map to corresponding child templates included + in renderspec which simply extend the '.spec' template. + """ + base_ref = '.spec' + template_postfix = '.spec.j2' + + def __init__(self, template_fn, encoding='utf-8'): + self.base_fn = template_fn + self.encoding = encoding + self.disttemp_path = get_dist_templates_path() + + def get_source(self, environment, template): + if template == self.base_ref: + fn = self.base_fn + else: + fn = os.path.join(self.disttemp_path, + template + self.template_postfix) + + f = open_if_exists(fn) + if not f: + return TemplateNotFound(template) + try: + contents = f.read().decode(self.encoding) + finally: + f.close() + + mtime = os.path.getmtime(self.base_fn) + + def uptodate(): + try: + return os.path.getmtime(self.base_fn) == mtime + except OSError: + return False + + return contents, fn, uptodate + + def list_templates(self): + found = set([self.base_ref]) + walk_dir = os.walk(self.disttemp_path) + for _, _, filenames in walk_dir: + for fn in filenames: + if fn.endswith(self.template_postfix): + template = fn[:-len(self.template_postfix)] + found.add(template) + return sorted(found) diff --git a/tests.py b/tests.py index 5b7169c..cc84bd5 100644 --- a/tests.py +++ b/tests.py @@ -24,7 +24,7 @@ from ddt import data, ddt, unpack from jinja2 import Environment -from mock import Mock +from mock import Mock, patch import os import renderspec import renderspec.versions @@ -297,5 +297,41 @@ class RenderspecDistroDetection(unittest.TestCase): self.assertEqual(renderspec._get_default_distro(), "unknown") +class RenderspecDistTeamplatesTests(unittest.TestCase): + @patch('renderspec.distloader.get_dist_templates_path') + def test_dist_templates(self, mock_dt_path): + base_txt = ('Line before block\n' + '{% block footest %}{% endblock %}\n' + 'Line after block\n') + dt_txt = ('{% extends ".spec" %}' + '{% block footest %}' + 'foo block\n' + 'macro: {{ py2pkg("test") }}\n' + '{% endblock %}') + expected_out = ('Line before block\n' + 'foo block\n' + 'macro: python-test\n' + 'Line after block') + tmpdir = tempfile.mkdtemp(prefix='renderspec-test_') + try: + # create .spec template + base_path = os.path.join(tmpdir, 'foo.spec.j2') + with open(base_path, 'w+') as f: + f.write(base_txt) + # create custom dist template + dt_dir = os.path.join(tmpdir, 'dist-templates') + os.mkdir(dt_dir) + dt_path = os.path.join(dt_dir, 'loldistro.spec.j2') + with open(dt_path, 'w+') as f: + f.write(dt_txt) + # mock this to use testing dist-tempaltes folder + mock_dt_path.return_value = dt_dir + + out = renderspec.generate_spec('loldistro', {}, {}, base_path) + self.assertEqual(out, expected_out) + finally: + shutil.rmtree(tmpdir) + + if __name__ == '__main__': unittest.main()