From 2e50640d8bf6390ab9f12d2369aadf045c3e7c19 Mon Sep 17 00:00:00 2001 From: Grant Murphy Date: Wed, 10 Dec 2014 14:27:33 -0800 Subject: [PATCH] Use templates to generate rst documentation Using Jinja2 to generate ReSTructured text documents via a Sphinx Extension. Change-Id: I384971732166fbeb123d572d3ccbcde6bad39dfc --- doc/source/_exts/rst.jinja | 69 ++++++++++++++++++++++ doc/source/_exts/vmt.py | 118 +++++++++++++++++++++++++++++++++++++ doc/source/conf.py | 3 +- doc/source/index.rst | 6 +- ossa/index.rst | 6 -- test-requirements.txt | 2 + tox.ini | 9 +-- 7 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 doc/source/_exts/rst.jinja create mode 100644 doc/source/_exts/vmt.py delete mode 100644 ossa/index.rst diff --git a/doc/source/_exts/rst.jinja b/doc/source/_exts/rst.jinja new file mode 100644 index 0000000..94a27af --- /dev/null +++ b/doc/source/_exts/rst.jinja @@ -0,0 +1,69 @@ +============= +{{ id }} +============= + +{{ title }} +{% for _ in title %}-{% endfor %} + + +Date +~~~~ +{{ date | ossa_date }} + + +Affects +~~~~~~~ +{% for affected in affected_products %} +- {{ affected['product'].capitalize() }}: {{ affected['version'] }} +{% endfor %} + + +Description +~~~~~~~~~~~ +{{ description }} + +{% if errata %} +Errata +~~~~~~ +{{ errata_text }} +{% endif %} +{% if notes %} +Notes +~~~~~ +{{ notes }} +{% endif %} + +Credits +~~~~~~~ +{% for reporter in reporters %} +{% if 'affiliation' in reporter and reporter['affiliation'] and reporter['affiliation'] != 'UNKNOWN' %} +- {{ reporter['name'] | trim | indent }} from {{ reporter ['affiliation'] }} ({{ reporter['reported'] | csv_list }}) +{% else %} +- {{ reporter['name'] | trim | indent }} ({{ reporter['reported'] | csv_list }}) +{% endif %} +{% endfor %} + + +References +~~~~~~~~~~ +{% for link in issues['links'] %} +- {{ link }} +{% endfor %} + + +Patches +~~~~~~~ +{% for release in reviews %} +{% if release != 'type' %} +{% for link in reviews[release] %} +- {{ link | trim }} ({{ release.capitalize() | trim }}) +{% endfor %} +{% endif %} +{% endfor %} +{% if errata %} +OSSA History +~~~~~~~~~~~~ +{% for change in errata_history %} +- {{ change }} +{% endfor %} +{% endif %} diff --git a/doc/source/_exts/vmt.py b/doc/source/_exts/vmt.py new file mode 100644 index 0000000..1c7ba8b --- /dev/null +++ b/doc/source/_exts/vmt.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +import glob +import os +import sys +import textwrap +import traceback + +from docutils import nodes +from docutils.parsers import rst +from docutils.statemachine import ViewList +from jinja2 import FileSystemLoader +from jinja2.environment import Environment +from sphinx.util.nodes import nested_parse_with_titles +from sphinx.addnodes import toctree +import yaml + +reload(sys) +sys.setdefaultencoding("utf-8") + + +def to_snake_case(d): + for k in d: + v = d[k] + del d[k] + d[k.replace('-', '_')] = v + if type(k) == dict: + to_snake_case(d) + + +def to_paragraphs(d, *args): + for k in args: + if type(d[k]) == str: + d[k] = '\n'.join(textwrap.wrap(d[k])) + + +def ossa_date_formatter(value): + return "{:%B %d, %Y}".format(value) + + +def csv_list_formatter(value): + return ", ".join(value) + + +def render_template(template, data, **kwargs): + template_dir = kwargs.get('template_dir', os.getcwd()) + loader = FileSystemLoader(template_dir) + env = Environment(trim_blocks=True, loader=loader) + env.filters["ossa_date"] = ossa_date_formatter + env.filters["csv_list"] = csv_list_formatter + template = env.get_template(template) + return template.render(**data) + + +def render(source, template, **kwargs): + vals = yaml.safe_load(open(source).read()) + to_snake_case(vals) + to_paragraphs(vals, 'description') + return render_template(template, vals, **kwargs) + + +class OSSADirective(rst.Directive): + + option_spec = { + "yaml_files": rst.directives.unchanged, + "jinja_template_dir": rst.directives.unchanged, + "jinja_template": rst.directives.unchanged + } + + has_content = False + + def report_error(self, message): + return [self.state_machine.reporter.error( + message, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno)] + + def run(self): + + env = self.state.document.settings.env + app = env.app + + yaml_files = self.options.get('yaml_files') + if not yaml_files: + return self.report_error('Location of OSSA data required') + + template_files = self.options.get("jinja_template_dir") + if not yaml_files: + return self.report_error('Template locationrequired') + + template_name = self.options.get("jinja_template") + if not template_name: + template_name = "rst.jinja" + + _, yaml_files = env.relfn2path(yaml_files) + app.info('Loading YAML files from {}..'.format(yaml_files)) + if not os.path.exists(yaml_files): + return self.report_error('Invalid OSSA directory') + + _, template_files = env.relfn2path(template_files) + app.info('Loading templates from {}..'.format(template_files)) + if not os.path.exists(template_files): + return self.report_error('No valid template found') + + input_files = glob.glob(os.path.join(yaml_files, "*.yaml")) + output_files = [x.replace(".yaml", ".rst") for x in input_files] + toc = [] + for old, new in zip(input_files, output_files): + with open(new, "w") as out: + out.write(render(old, template_name, + template_dir=template_files)) + toc.append(out) + + return toctree(toc).children + + +def setup(app): + app.info('Loading the vmt extension') + app.add_directive('vmt', OSSADirective) diff --git a/doc/source/conf.py b/doc/source/conf.py index 25cf7bc..7b7ee32 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -27,7 +27,8 @@ sys.path.insert(0, os.path.join(os.path.abspath('.'), '_exts')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'oslosphinx', + 'vmt', + 'oslosphinx' ] todo_include_todos = True diff --git a/doc/source/index.rst b/doc/source/index.rst index 0bb2b7a..822f187 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,11 +4,15 @@ These pages contain published OpenStack Security Advisories. +.. vmt:: + :yaml_files: ../../ossa + :jinja_template_dir: _exts/ + .. toctree:: :maxdepth: 2 :glob: - ossa/index + ./ossa/* .. seealso:: diff --git a/ossa/index.rst b/ossa/index.rst deleted file mode 100644 index e268873..0000000 --- a/ossa/index.rst +++ /dev/null @@ -1,6 +0,0 @@ -=============================== - OpenStack Security Advisories -=============================== - -Published OpenStack Security Advisories. - diff --git a/test-requirements.txt b/test-requirements.txt index 8e7314b..9d515c0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,5 @@ # needed for doc build +Jinja2>=2.7.3 +PyYAML==3.11 sphinx>=1.1.2,!=1.2.0,<1.3 oslosphinx>=2.2.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index d16e0f4..930acfe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = docs +envlist = venv minversion = 1.6 skipsdist = True @@ -7,10 +7,7 @@ skipsdist = True usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} -deps = -r{toxinidir}/test-requirements.txt [testenv:venv] -commands = {posargs} - -[testenv:docs] -commands = python setup.py build_sphinx +deps = -r{toxinidir}/test-requirements.txt +commands =python setup.py build_sphinx