1# Copyright 2023 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Sphinx directives for Pigweed SEEDs""" 15 16 17import docutils 18from docutils import nodes 19import docutils.statemachine 20 21# pylint: disable=consider-using-from-import 22import docutils.parsers.rst.directives as directives # type: ignore 23 24# pylint: enable=consider-using-from-import 25from sphinx.application import Sphinx as SphinxApplication 26from sphinx.util.docutils import SphinxDirective 27 28from sphinx_design.cards import CardDirective 29 30 31def status_choice(arg) -> str: 32 return directives.choice( 33 arg, 34 ( 35 'draft', 36 'open_for_comments', 37 'intent_approved', 38 'last_call', 39 'accepted', 40 'rejected', 41 ), 42 ) 43 44 45def parse_status(arg) -> str: 46 """Support variations on the status choices. 47 48 For example, you can use capital letters and spaces. 49 """ 50 51 return status_choice('_'.join([token.lower() for token in arg.split(' ')])) 52 53 54def status_badge(seed_status: str, badge_status) -> str: 55 """Given a SEED status, return the status badge for rendering.""" 56 57 return ( 58 ':bdg-primary:' 59 if seed_status == badge_status 60 else ':bdg-secondary-line:' 61 ) 62 63 64def cl_link(cl_num): 65 return ( 66 f'`pwrev/{cl_num} ' 67 '<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/' 68 f'{cl_num}>`_' 69 ) 70 71 72class PigweedSeedDirective(SphinxDirective): 73 """Directive registering & rendering SEED metadata.""" 74 75 required_arguments = 0 76 final_argument_whitespace = True 77 has_content = True 78 option_spec = { 79 'number': directives.positive_int, 80 'name': directives.unchanged_required, 81 'status': parse_status, 82 'proposal_date': directives.unchanged_required, 83 'cl': directives.positive_int_list, 84 'authors': directives.unchanged_required, 85 'facilitator': directives.unchanged_required, 86 } 87 88 def _try_get_option(self, option: str): 89 """Try to get an option by name and raise on failure.""" 90 91 try: 92 return self.options[option] 93 except KeyError: 94 raise self.error(f' :{option}: option is required') 95 96 def run(self) -> list[nodes.Node]: 97 seed_number = '{:04d}'.format(self._try_get_option('number')) 98 seed_name = self._try_get_option('name') 99 status = self._try_get_option('status') 100 proposal_date = self._try_get_option('proposal_date') 101 cl_nums = self._try_get_option('cl') 102 authors = self._try_get_option('authors') 103 facilitator = self._try_get_option('facilitator') 104 105 title = ( 106 f':fas:`seedling` SEED-{seed_number}: :ref:' 107 f'`{seed_name}<seed-{seed_number}>`\n' 108 ) 109 110 authors_heading = 'Authors' if len(authors.split(',')) > 1 else 'Author' 111 112 self.content = docutils.statemachine.StringList( 113 [ 114 ':octicon:`comment-discussion` Status:', 115 f'{status_badge(status, "open_for_comments")}' 116 '`Open for Comments`', 117 ':octicon:`chevron-right`', 118 f'{status_badge(status, "intent_approved")}' 119 '`Intent Approved`', 120 ':octicon:`chevron-right`', 121 f'{status_badge(status, "last_call")}`Last Call`', 122 ':octicon:`chevron-right`', 123 f'{status_badge(status, "accepted")}`Accepted`', 124 ':octicon:`kebab-horizontal`', 125 f'{status_badge(status, "rejected")}`Rejected`', 126 '\n', 127 f':octicon:`calendar` Proposal Date: {proposal_date}', 128 '\n', 129 ':octicon:`code-review` CL: ', 130 ', '.join([cl_link(cl_num) for cl_num in cl_nums]), 131 '\n', 132 f':octicon:`person` {authors_heading}: {authors}', 133 '\n', 134 f':octicon:`person` Facilitator: {facilitator}', 135 ] 136 ) 137 138 card = CardDirective.create_card( 139 inst=self, 140 arguments=[title], 141 options={}, 142 ) 143 144 return [card] 145 146 147def setup(app: SphinxApplication): 148 app.add_directive('seed', PigweedSeedDirective) 149 150 return { 151 'parallel_read_safe': True, 152 'parallel_write_safe': True, 153 } 154