diff --git a/doc/source/_exts/atcs.py b/doc/source/_exts/atcs.py new file mode 100644 index 000000000..5a4eae915 --- /dev/null +++ b/doc/source/_exts/atcs.py @@ -0,0 +1,152 @@ +# 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. + +"""Show information about extra ATCs managed in this repo. +""" + +import os +import re + +from docutils import nodes +from docutils.parsers import rst +from docutils.parsers.rst import directives +from docutils.parsers.rst.directives.tables import Table +from docutils.statemachine import ViewList +from sphinx.util.nodes import nested_parse_with_titles + +_atcs_by_project = {} + + +class ExtraATCsTable(Table): + """List the extra ATCs for the given project. + """ + + HEADERS = ('Full Name', 'Email', 'Expires In') + HEADER_MAP = { + 'Full Name': 'name', + 'Email': 'email', + 'Expires In': 'expires_in', + } + + option_spec = {'class': directives.class_option, + 'name': directives.unchanged, + 'project': directives.unchanged, + } + + def run(self): + env = self.state.document.settings.env + app = env.app + + project = self.options.get('project') + if not project: + error = self.state_machine.reporter.error( + 'No project set for extra-atcs table', + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [error] + + # Handle the width settings and title + try: + col_widths = self.get_column_widths(len(self.HEADERS)) + title, messages = self.make_title() + except SystemMessagePropagation, detail: + return [detail.args[0]] + except Exception, err: + error = self.state_machine.reporter.error( + 'Error processing memberstable directive:\n%s' % err, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno, + ) + return [error] + + project_members = _atcs_by_project.get(project, []) + + # If we have no extra ATCs, skip building the table. + if not project_members: + return [] + + table_node = self.build_table(project_members, col_widths) + table_node['classes'] += self.options.get('class', []) + self.add_name(table_node) + + if title: + table_node.insert(0, title) + + return [table_node] + messages + + def build_table(self, project_members, col_widths): + table = nodes.table() + + # Set up the column specifications + # based on the widths. + tgroup = nodes.tgroup(cols=len(col_widths)) + table += tgroup + tgroup.extend(nodes.colspec(colwidth=col_width) + for col_width in col_widths) + + # Set the headers + thead = nodes.thead() + tgroup += thead + row_node = nodes.row() + thead += row_node + row_node.extend( + nodes.entry(h, nodes.paragraph(text=h)) + for h in self.HEADERS + ) + + # The body of the table is made up of rows. + # Each row contains a series of entries, + # and each entry contains a paragraph of text. + tbody = nodes.tbody() + tgroup += tbody + rows = [] + for row in project_members: + trow = nodes.row() + # Iterate over the headers in the same order every time. + for h in self.HEADERS: + # Get the cell value from the row data, replacing None + # in re match group with empty string. + cell = row.get(self.HEADER_MAP[h]) or '' + entry = nodes.entry() + para = nodes.paragraph(text=unicode(cell)) + entry += para + trow += entry + rows.append(trow) + tbody.extend(rows) + + return table + + +_PATTERN = re.compile('(?P.+):\s+(?P.+)\s\((?P.+)\)\s\[(?P.*)\]') + + +def _build_atcs_by_project(app): + filename = os.path.abspath('reference/extra-atcs') + with open(filename, 'r') as f: + for linum, line in enumerate(f, 1): + line = line.strip() + if not line or line.startswith('#'): + continue + m = _PATTERN.match(line) + if not m: + app.warn('Could not parse line %d of %s: %r' % + (linum, filename, line)) + continue + info = m.groupdict() + project = info['project'] + _atcs_by_project.setdefault(project, []).append(info) + + +def setup(app): + app.info('loading atcs extension') + app.add_directive('extraatcstable', ExtraATCsTable) + _build_atcs_by_project(app) diff --git a/doc/source/_exts/teams.py b/doc/source/_exts/teams.py index 44688f095..d8613e188 100644 --- a/doc/source/_exts/teams.py +++ b/doc/source/_exts/teams.py @@ -51,6 +51,8 @@ def _team_to_rst(name, info): tag_references = '' yield '- :repo:`%s` %s' % (project['repo'], tag_references) yield '' + yield '.. extraatcstable:: :ref:`Extra ATCs `' + yield ' :project: %s' % name yield '' diff --git a/doc/source/conf.py b/doc/source/conf.py index 3722c001b..7cf4585b5 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -29,6 +29,7 @@ sys.path.insert(0, os.path.join(os.path.abspath('.'), '_exts')) extensions = [ 'sphinx.ext.extlinks', 'oslosphinx', + 'atcs', 'members', 'projects', 'teams', diff --git a/reference/charter.rst b/reference/charter.rst index c4d3e9a4f..03719eec5 100644 --- a/reference/charter.rst +++ b/reference/charter.rst @@ -127,6 +127,8 @@ multiple-winner election system (see below). The election is held 3 weeks prior to each design summit, with nominations due 4 weeks prior to the summit and elections held open for no less than five business days. +.. _atc: + Voters for TC seats ("ATC") ===========================