aboutsummaryrefslogtreecommitdiff
path: root/pw_docgen/py/pw_docgen/sphinx/module_metadata.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_docgen/py/pw_docgen/sphinx/module_metadata.py')
-rw-r--r--pw_docgen/py/pw_docgen/sphinx/module_metadata.py226
1 files changed, 226 insertions, 0 deletions
diff --git a/pw_docgen/py/pw_docgen/sphinx/module_metadata.py b/pw_docgen/py/pw_docgen/sphinx/module_metadata.py
new file mode 100644
index 000000000..d6fe05497
--- /dev/null
+++ b/pw_docgen/py/pw_docgen/sphinx/module_metadata.py
@@ -0,0 +1,226 @@
+# Copyright 2023 The Pigweed Authors
+#
+# 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
+#
+# https://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.
+"""Sphinx directives for Pigweed module metadata"""
+
+from typing import List
+
+import docutils
+
+# pylint: disable=consider-using-from-import
+import docutils.parsers.rst.directives as directives # type: ignore
+
+# pylint: enable=consider-using-from-import
+from sphinx.application import Sphinx as SphinxApplication
+from sphinx.util.docutils import SphinxDirective
+from sphinx_design.badges_buttons import ButtonRefDirective # type: ignore
+from sphinx_design.cards import CardDirective # type: ignore
+
+
+def status_choice(arg):
+ return directives.choice(arg, ('experimental', 'unstable', 'stable'))
+
+
+def status_badge(module_status: str) -> str:
+ role = ':bdg-primary:'
+ return role + f'`{module_status.title()}`'
+
+
+def cs_url(module_name: str):
+ return f'https://cs.opensource.google/pigweed/pigweed/+/main:{module_name}/'
+
+
+def concat_tags(*tag_lists: List[str]):
+ all_tags = tag_lists[0]
+
+ for tag_list in tag_lists[1:]:
+ if len(tag_list) > 0:
+ all_tags.append(':octicon:`dot-fill`')
+ all_tags.extend(tag_list)
+
+ return all_tags
+
+
+class PigweedModuleDirective(SphinxDirective):
+ """Directive registering module metadata, rendering title & info card."""
+
+ required_arguments = 0
+ final_argument_whitespace = True
+ has_content = True
+ option_spec = {
+ 'name': directives.unchanged_required,
+ 'tagline': directives.unchanged_required,
+ 'status': status_choice,
+ 'is-deprecated': directives.flag,
+ 'languages': directives.unchanged,
+ 'code-size-impact': directives.unchanged,
+ 'facade': directives.unchanged,
+ 'get-started': directives.unchanged_required,
+ 'tutorials': directives.unchanged,
+ 'guides': directives.unchanged,
+ 'concepts': directives.unchanged,
+ 'design': directives.unchanged_required,
+ 'api': directives.unchanged,
+ }
+
+ def try_get_option(self, option: str):
+ try:
+ return self.options[option]
+ except KeyError:
+ raise self.error(f' :{option}: option is required')
+
+ def maybe_get_option(self, option: str):
+ try:
+ return self.options[option]
+ except KeyError:
+ return None
+
+ def create_section_button(self, title: str, ref: str):
+ node = docutils.nodes.list_item(classes=['pw-module-section-button'])
+ node += ButtonRefDirective(
+ name='',
+ arguments=[ref],
+ options={'color': 'primary'},
+ content=[title],
+ lineno=0,
+ content_offset=0,
+ block_text='',
+ state=self.state,
+ state_machine=self.state_machine,
+ ).run()
+
+ return node
+
+ def register_metadata(self):
+ module_name = self.try_get_option('name')
+
+ if 'facade' in self.options:
+ facade = self.options['facade']
+
+ # Initialize the module relationship dict if needed
+ if not hasattr(self.env, 'pw_module_relationships'):
+ self.env.pw_module_relationships = {}
+
+ # Initialize the backend list for this facade if needed
+ if facade not in self.env.pw_module_relationships:
+ self.env.pw_module_relationships[facade] = []
+
+ # Add this module as a backend of the provided facade
+ self.env.pw_module_relationships[facade].append(module_name)
+
+ if 'is-deprecated' in self.options:
+ # Initialize the deprecated modules list if needed
+ if not hasattr(self.env, 'pw_modules_deprecated'):
+ self.env.pw_modules_deprecated = []
+
+ self.env.pw_modules_deprecated.append(module_name)
+
+ def run(self):
+ tagline = docutils.nodes.paragraph(
+ text=self.try_get_option('tagline'),
+ classes=['section-subtitle'],
+ )
+
+ status_tags: List[str] = [
+ status_badge(self.try_get_option('status')),
+ ]
+
+ if 'is-deprecated' in self.options:
+ status_tags.append(':bdg-danger:`Deprecated`')
+
+ language_tags = []
+
+ if 'languages' in self.options:
+ languages = self.options['languages'].split(',')
+
+ if len(languages) > 0:
+ for language in languages:
+ language_tags.append(f':bdg-info:`{language.strip()}`')
+
+ code_size_impact = []
+
+ if code_size_text := self.maybe_get_option('code-size-impact'):
+ code_size_impact.append(f'**Code Size Impact:** {code_size_text}')
+
+ # Move the directive content into a section that we can render wherever
+ # we want.
+ content = docutils.nodes.paragraph()
+ self.state.nested_parse(self.content, 0, content)
+
+ # The card inherits its content from this node's content, which we've
+ # already pulled out. So we can replace this node's content with the
+ # content we need in the card.
+ self.content = docutils.statemachine.StringList(
+ concat_tags(status_tags, language_tags, code_size_impact)
+ )
+
+ card = CardDirective.create_card(
+ inst=self,
+ arguments=[],
+ options={},
+ )
+
+ # Create the top-level section buttons.
+ section_buttons = docutils.nodes.bullet_list(
+ classes=['pw-module-section-buttons']
+ )
+
+ # This is the pattern for required sections.
+ section_buttons += self.create_section_button(
+ 'Get Started', self.try_get_option('get-started')
+ )
+
+ # This is the pattern for optional sections.
+ if (tutorials_ref := self.maybe_get_option('tutorials')) is not None:
+ section_buttons += self.create_section_button(
+ 'Tutorials', tutorials_ref
+ )
+
+ if (guides_ref := self.maybe_get_option('guides')) is not None:
+ section_buttons += self.create_section_button('Guides', guides_ref)
+
+ if (concepts_ref := self.maybe_get_option('concepts')) is not None:
+ section_buttons += self.create_section_button(
+ 'Concepts', concepts_ref
+ )
+
+ section_buttons += self.create_section_button(
+ 'Design', self.try_get_option('design')
+ )
+
+ if (api_ref := self.maybe_get_option('api')) is not None:
+ section_buttons += self.create_section_button(
+ 'API Reference', api_ref
+ )
+
+ return [tagline, section_buttons, content, card]
+
+
+def build_backend_lists(app, _doctree, _fromdocname):
+ env = app.builder.env
+
+ if not hasattr(env, 'pw_module_relationships'):
+ env.pw_module_relationships = {}
+
+
+def setup(app: SphinxApplication):
+ app.add_directive('pigweed-module', PigweedModuleDirective)
+
+ # At this event, the documents and metadata have been generated, and now we
+ # can modify the doctree to reflect the metadata.
+ app.connect('doctree-resolved', build_backend_lists)
+
+ return {
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }