# Copyright (C) 2014 Red Hat, Inc. # # 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. """ This provides a sphinx extension able to render the source/support-matrix.ini file into the developer documentation. It is used via a single directive in the .rst file .. support_matrix:: """ import configparser as config_parser import os from docutils import nodes from docutils.parsers import rst from sphinx.util import logging from sphinx.util.osutil import copyfile from designate.backend.base import Backend LOG = logging.getLogger(__name__) class SupportMatrix: """Represents the entire support matrix for Nova virt drivers """ def __init__(self): # List of SupportMatrixFeature instances, describing # all the features present in Nova virt drivers self.grades = [] self.grade_names = {} self.grade_classes = {} # Dict of (name, SupportMatrixTarget) enumerating # all the hypervisor drivers that have data recorded # for them in self.features. The 'name' dict key is # the value from the SupportMatrixTarget.key attribute self.backends = {} class SupportMatrixGrade: def __init__(self, key, title, notes, in_tree, css_class): self.key = key self.title = title self.notes = notes self.in_tree = in_tree self.css_class = css_class class SupportMatrixBackend: def __init__(self, key, title, status, maintainers=None, variations=None, repository=None, type=None): self.key = key self.title = title self.type = type self.status = status self.maintainers = maintainers self.variations = variations self.repository = repository class SupportMatrixDirective(rst.Directive): option_spec = { 'support-matrix': str, } def run(self): matrix = self._load_support_matrix() return self._build_markup(matrix) def _load_support_matrix(self): """Reads the support-matrix.ini file and populates an instance of the SupportMatrix class with all the data. :returns: SupportMatrix instance """ cfg = config_parser.ConfigParser() env = self.state.document.settings.env fname = self.options.get("support-matrix", "support-matrix.ini") rel_fpath, fpath = env.relfn2path(fname) with open(fpath) as fp: cfg.read_file(fp) # This ensures that the docs are rebuilt whenever the # .ini file changes env.note_dependency(rel_fpath) matrix = SupportMatrix() # The 'targets' section is special - it lists all the # hypervisors that this file records data for for item in cfg.options("backends"): if not item.startswith("backend-impl-"): continue # The driver string will optionally contain # a hypervisor and architecture qualifier # so we expect between 1 and 3 components # in the name key = item[13:] title = cfg.get("backends", item) name = key.split("-") try: status = cfg.get("backends.%s" % item, "status") except config_parser.NoOptionError: if cfg.get("backends.%s" % item, "type") == "xfr": backend = Backend.get_driver(name[0]) status = backend.__backend_status__ if len(name) == 1: backend = SupportMatrixBackend( key, title, status, name[0]) elif len(name) == 2: backend = SupportMatrixBackend( key, title, status, name[0], variations=name[1]) else: raise Exception("'%s' field is malformed in '[%s]' section" % (item, "DEFAULT")) backend.in_tree = cfg.getboolean( "backends.%s" % item, "in-tree") backend.type = cfg.get( "backends.%s" % item, "type") backend.notes = cfg.get( "backends.%s" % item, "notes") backend.repository = cfg.get( "backends.%s" % item, "repository") backend.maintainers = cfg.get( "backends.%s" % item, "maintainers") matrix.backends[key] = backend grades = cfg.get("grades", "valid-grades") grades = grades.split(",") for grade in grades: title = cfg.get("grades.%s" % grade, "title") notes = cfg.get("grades.%s" % grade, "notes") in_tree = cfg.get("grades.%s" % grade, "in-tree") css_class = cfg.get("grades.%s" % grade, "css-class") matrix.grade_names[grade] = title matrix.grade_classes[grade] = css_class grade = SupportMatrixGrade( grade, title, notes, in_tree, css_class) matrix.grades.append(grade) return matrix def _build_markup(self, matrix): """Constructs the docutils content for the support matrix """ content = [] self._build_grade_listing(matrix, content) self._build_grade_table(matrix, content) self._build_backend_detail(matrix, content) return content def _build_backend_detail_table(self, backend, matrix): table = nodes.table() table['classes'].append("table") table['classes'].append("table-condensed") tgroup = nodes.tgroup(cols=2) tbody = nodes.tbody() for i in range(2): tgroup.append(nodes.colspec(colwidth=1)) tgroup.append(tbody) table.append(tgroup) graderow = nodes.row() gradetitle = nodes.entry() gradetitle.append(nodes.strong(text="Grade")) gradetext = nodes.entry() class_name = "label-%s" % matrix.grade_classes[backend.status] status_text = nodes.paragraph( text=matrix.grade_names[backend.status]) status_text['classes'].append(class_name) status_text['classes'].append("label") gradetext.append(status_text) graderow.append(gradetitle) graderow.append(gradetext) tbody.append(graderow) treerow = nodes.row() treetitle = nodes.entry() treetitle.append(nodes.strong(text="In Tree")) if bool(backend.in_tree): status = "\u2714" intree = nodes.paragraph(text=status) intree['classes'].append("label") intree['classes'].append("label-success") else: status = "\u2716" intree = nodes.paragraph(text=status) intree['classes'].append("label") intree['classes'].append("label-danger") status = "\u2714" treetext = nodes.entry() treetext.append(intree) treerow.append(treetitle) treerow.append(treetext) tbody.append(treerow) maintrow = nodes.row() mainttitle = nodes.entry() mainttitle.append(nodes.strong(text="Maintainers")) mainttext = nodes.entry() mainttext.append(nodes.paragraph(text=backend.maintainers)) maintrow.append(mainttitle) maintrow.append(mainttext) tbody.append(maintrow) reporow = nodes.row() repotitle = nodes.entry() repotitle.append(nodes.strong(text="Repository")) repotext = nodes.entry() repotext.append(nodes.paragraph(text=backend.repository)) reporow.append(repotitle) reporow.append(repotext) tbody.append(reporow) noterow = nodes.row() notetitle = nodes.entry() notetitle.append(nodes.strong(text="Notes")) notetext = nodes.entry() notetext.append(nodes.paragraph(text=backend.notes)) noterow.append(notetitle) noterow.append(notetext) tbody.append(noterow) return table def _build_backend_detail(self, matrix, content): detailstitle = nodes.subtitle(text="Backend Details") content.append(detailstitle) content.append(nodes.paragraph()) for key in matrix.backends.keys(): content.append( nodes.subtitle(text=matrix.backends[key].title)) content.append( self._build_backend_detail_table( matrix.backends[key], matrix)) content.append(nodes.paragraph()) return content def _build_grade_listing(self, matrix, content): summarytitle = nodes.subtitle(text="Grades") content.append(nodes.raw(text="Grades", attributes={'tagname': 'h2'})) content.append(summarytitle) table = nodes.table() table['classes'].append("table") table['classes'].append("table-condensed") grades = matrix.grades tablegroup = nodes.tgroup(cols=2) summarybody = nodes.tbody() summaryhead = nodes.thead() for i in range(2): tablegroup.append(nodes.colspec(colwidth=1)) tablegroup.append(summaryhead) tablegroup.append(summarybody) table.append(tablegroup) content.append(table) header = nodes.row() blank = nodes.entry() blank.append(nodes.strong(text="Grade")) header.append(blank) blank = nodes.entry() blank.append(nodes.strong(text="Description")) header.append(blank) summaryhead.append(header) for grade in grades: item = nodes.row() namecol = nodes.entry() class_name = "label-%s" % grade.css_class status_text = nodes.paragraph(text=grade.title) status_text['classes'].append(class_name) status_text['classes'].append("label") namecol.append(status_text) item.append(namecol) notescol = nodes.entry() notescol.append(nodes.paragraph(text=grade.notes)) item.append(notescol) summarybody.append(item) return content def _build_grade_table(self, matrix, content): summarytitle = nodes.subtitle(text="Backends - Summary") summary = nodes.table() summary['classes'].append("table") summary['classes'].append("table-condensed") summarygroup = nodes.tgroup(cols=5) summarybody = nodes.tbody() summaryhead = nodes.thead() for i in range(5): summarygroup.append(nodes.colspec(colwidth=1)) summarygroup.append(summaryhead) summarygroup.append(summarybody) summary.append(summarygroup) content.append(summarytitle) content.append(summary) header = nodes.row() blank = nodes.entry() blank.append(nodes.strong(text="Backend")) header.append(blank) blank = nodes.entry() blank.append(nodes.strong(text="Status")) header.append(blank) blank = nodes.entry() blank.append(nodes.strong(text="Type")) header.append(blank) blank = nodes.entry() blank.append(nodes.strong(text="In Tree")) header.append(blank) blank = nodes.entry() blank.append(nodes.strong(text="Notes")) header.append(blank) summaryhead.append(header) grades = matrix.grades impls = list(matrix.backends.keys()) impls.sort() for grade in grades: for backend in impls: if matrix.backends[backend].status == grade.key: item = nodes.row() namecol = nodes.entry() namecol.append( nodes.paragraph(text=matrix.backends[backend].title)) item.append(namecol) statuscol = nodes.entry() class_name = "label-%s" % grade.css_class status_text = nodes.paragraph(text=grade.title) status_text['classes'].append(class_name) status_text['classes'].append("label") statuscol.append(status_text) item.append(statuscol) typecol = nodes.entry() type_text = nodes.paragraph( text=matrix.backends[backend].type) type_text['classes'].append("label") type_text['classes'].append("label-info") typecol.append(type_text) item.append(typecol) if bool(matrix.backends[backend].in_tree): status = "\u2714" intree = nodes.paragraph(text=status) intree['classes'].append("label") intree['classes'].append("label-success") else: status = "\u2716" intree = nodes.paragraph(text=status) intree['classes'].append("label") intree['classes'].append("label-danger") intreecol = nodes.entry() intreecol.append(intree) item.append(intreecol) notescol = nodes.entry() notescol.append(nodes.paragraph( text=matrix.backends[backend].notes)) item.append(notescol) summarybody.append(item) return content def copy_assets(app, exception): assets = ['support-matrix.css', 'support-matrix.js'] if app.builder.name != 'html' or exception: return LOG.info('Copying assets: %s' % ', '.join(assets)) for asset in assets: dest = os.path.join(app.builder.outdir, '_static', asset) source = os.path.abspath(os.path.dirname(__file__)) copyfile(os.path.join(source, 'assets', asset), dest) def add_assets(app): app.add_css_file('support-matrix.css') app.add_js_file('support-matrix.js') def setup(app): # Add all the static assets to our build during the early stage of building app.connect('builder-inited', add_assets) # This copies all the assets (css, js, fonts) over to the build # _static directory during final build. app.connect('build-finished', copy_assets) app.add_directive('support_matrix', SupportMatrixDirective)