From ce6a7fe4fc9949787f768b8e743b38ae017fe0ea Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:17:51 +1200 Subject: [PATCH] docs/util: Cells now have properties Properties are both an option: ``` .. cell:def:: $add :properties: is_evaluable ``` and a field: ``` .. cell:def:: $eqx :property x-aware: :property is_evaluable: ``` Properties as an option appear in the property index: linking a given property to all cells with that property; while properties as a field display with the cell. --- docs/util/cellref.py | 15 +++- docs/util/cmdref.py | 190 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 184 insertions(+), 21 deletions(-) diff --git a/docs/util/cellref.py b/docs/util/cellref.py index 3bba27876..71e8eecd1 100644 --- a/docs/util/cellref.py +++ b/docs/util/cellref.py @@ -33,7 +33,7 @@ class YosysCell: code: str inputs: list[str] outputs: list[str] - properties: dict[str, bool] + properties: list[str] class YosysCellGroupDocumenter(Documenter): objtype = 'cellgroup' @@ -298,11 +298,22 @@ class YosysCellDocumenter(YosysCellGroupDocumenter): self.add_line(f'.. {domain}:{directive}:: {sig}', sourcename) # options - opt_attrs = ["title", ] + opt_attrs = ["title", "properties", ] for attr in opt_attrs: val = getattr(cell, attr, None) + if isinstance(val, list): + val = ' '.join(val) if val: self.add_line(f' :{attr}: {val}', sourcename) + + self.add_line('\n', sourcename) + + # fields + field_attrs = ["properties", ] + for field in field_attrs: + attr = getattr(cell, field, []) + for val in attr: + self.add_line(f' :{field} {val}:', sourcename) if self.options.noindex: self.add_line(' :noindex:', sourcename) diff --git a/docs/util/cmdref.py b/docs/util/cmdref.py index c7bb4e241..c177fdfa6 100644 --- a/docs/util/cmdref.py +++ b/docs/util/cmdref.py @@ -2,17 +2,22 @@ from __future__ import annotations +import re +from typing import cast + from docutils import nodes -from docutils.nodes import Node, Element +from docutils.nodes import Node, Element, system_message from docutils.parsers.rst import directives from docutils.parsers.rst.states import Inliner from sphinx.application import Sphinx from sphinx.domains import Domain, Index from sphinx.domains.std import StandardDomain +from sphinx.environment import BuildEnvironment from sphinx.roles import XRefRole from sphinx.directives import ObjectDescription from sphinx.directives.code import container_wrapper from sphinx.util.nodes import make_refnode +from sphinx.util.docfields import Field from sphinx import addnodes class TocNode(ObjectDescription): @@ -70,7 +75,8 @@ class CommandNode(TocNode): return signode['fullname'] def add_target_and_index(self, name_cls, sig, signode): - signode['ids'].append(type(self).name + '-' + sig) + idx = type(self).name + '-' + sig + signode['ids'].append(idx) if 'noindex' not in self.options: name = "{}.{}.{}".format(self.name, type(self).__name__, sig) tagmap = self.env.domaindata[type(self).name]['obj2tag'] @@ -79,13 +85,81 @@ class CommandNode(TocNode): titlemap = self.env.domaindata[type(self).name]['obj2title'] titlemap[name] = title objs = self.env.domaindata[type(self).name]['objects'] + # (name, sig, typ, docname, anchor, prio) objs.append((name, sig, - title, + type(self).name, self.env.docname, - type(self).name + '-' + sig, + idx, 0)) +class PropNode(TocNode): + name = 'prop' + fieldname = 'props' + + def handle_signature(self, sig: str, signode: addnodes.desc_signature): + signode['fullname'] = sig + signode['tocname'] = tocname = sig.split('::')[-1] + signode += addnodes.desc_name(text=tocname) + return signode['fullname'] + + def add_target_and_index( + self, + name: str, + sig: str, + signode: addnodes.desc_signature + ) -> None: + idx = ".".join(name.split("::")) + signode['ids'].append(idx) + if 'noindex' not in self.options: + tocname: str = signode.get('tocname', name) + objs = self.env.domaindata[self.domain]['objects'] + # (name, sig, typ, docname, anchor, prio) + objs.append((name, + tocname, + type(self).name, + self.env.docname, + idx, + 1)) + +class CellGroupedField(Field): + """Custom version of GroupedField which doesn't require content.""" + is_grouped = True + list_type = nodes.bullet_list + + def __init__(self, name: str, names: tuple[str, ...] = (), label: str = None, + rolename: str = None, can_collapse: bool = False) -> None: + super().__init__(name, names, label, True, rolename) + self.can_collapse = can_collapse + + def make_field(self, types: dict[str, list[Node]], domain: str, + items: tuple, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> nodes.field: + fieldname = nodes.field_name('', self.label) + listnode = self.list_type() + for fieldarg, content in items: + par = nodes.paragraph() + if fieldarg: + par.extend(self.make_xrefs(self.rolename, domain, + fieldarg, nodes.Text, + env=env, inliner=inliner, location=location)) + + if len(content) == 1 and ( + isinstance(content[0], nodes.Text) or + (isinstance(content[0], nodes.inline) and len(content[0]) == 1 and + isinstance(content[0][0], nodes.Text))): + par += nodes.Text(' -- ') + par += content + listnode += nodes.list_item('', par) + + if len(items) == 1 and self.can_collapse: + list_item = cast(nodes.list_item, listnode[0]) + fieldbody = nodes.field_body('', list_item[0]) + return nodes.field('', fieldname, fieldbody) + + fieldbody = nodes.field_body('', listnode) + return nodes.field('', fieldname, fieldbody) + class CellNode(TocNode): """A custom node that describes an internal cell.""" @@ -94,8 +168,15 @@ class CellNode(TocNode): option_spec = { 'title': directives.unchanged, 'ports': directives.unchanged, + 'properties': directives.unchanged, } + doc_field_types = [ + CellGroupedField('props', label='Properties', rolename='prop', + names=('properties', 'property', 'tag', 'tags'), + can_collapse=True), + ] + def handle_signature(self, sig: str, signode: addnodes.desc_signature): signode['fullname'] = sig signode['tocname'] = tocname = sig.split('::')[-1] @@ -113,16 +194,18 @@ class CellNode(TocNode): signode['ids'].append(idx) if 'noindex' not in self.options: tocname: str = signode.get('tocname', name) - tagmap = self.env.domaindata[self.domain]['obj2tag'] - tagmap[name] = list(self.options.get('tags', '').split(' ')) title: str = self.options.get('title', sig) titlemap = self.env.domaindata[self.domain]['obj2title'] titlemap[name] = title + props = self.options.get('properties', '') + if props: + propmap = self.env.domaindata[self.domain]['obj2prop'] + propmap[name] = props.split(' ') objs = self.env.domaindata[self.domain]['objects'] # (name, sig, typ, docname, anchor, prio) objs.append((name, tocname, - title, + type(self).name, self.env.docname, idx, 0)) @@ -288,7 +371,7 @@ class TagIndex(Index): in self.domain.get_objects()} tmap = {} - tags = self.domain.data['obj2tag'] + tags = self.domain.data[f'obj2{self.name}'] for name, tags in tags.items(): for tag in tags: tmap.setdefault(tag,[]) @@ -304,10 +387,9 @@ class TagIndex(Index): anchor, docname, '', typ )) - re = [(k, v) for k, v in sorted(content.items())] - - return (re, True) + ret = [(k, v) for k, v in sorted(content.items())] + return (ret, True) class CommandIndex(Index): name = 'cmd' @@ -345,7 +427,8 @@ class CommandIndex(Index): content = {} items = ((name, dispname, typ, docname, anchor) for name, dispname, typ, docname, anchor, prio - in self.domain.get_objects()) + in self.domain.get_objects() + if typ == self.name) items = sorted(items, key=lambda item: item[0]) for name, dispname, typ, docname, anchor in items: lis = content.setdefault(self.shortname, []) @@ -354,15 +437,68 @@ class CommandIndex(Index): anchor, '', '', typ )) - re = [(k, v) for k, v in sorted(content.items())] + ret = [(k, v) for k, v in sorted(content.items())] - return (re, True) + return (ret, True) class CellIndex(CommandIndex): name = 'cell' localname = 'Internal cell reference' shortname = 'Internal cell' +class PropIndex(TagIndex): + """A custom directive that creates a properties matrix.""" + + name = 'prop' + localname = 'Property Index' + shortname = 'Prop' + fieldname = 'props' + + def generate(self, docnames=None): + content = {} + + cells = {name: (dispname, docname, anchor) + for name, dispname, typ, docname, anchor, _ + in self.domain.get_objects() + if typ == 'cell'} + props = {name: (dispname, docname, anchor) + for name, dispname, typ, docname, anchor, _ + in self.domain.get_objects() + if typ == 'prop'} + + tmap: dict[str, list[str]] = {} + tags: dict[str, list[str]] = self.domain.data[f'obj2{self.name}'] + for name, tags in tags.items(): + for tag in tags: + tmap.setdefault(tag,[]) + tmap[tag].append(name) + + for tag in sorted(tmap.keys()): + test = re.match(r'^(\w+[_-])', tag) + tag_prefix = test.group(1) + lis = content.setdefault(tag_prefix, []) + try: + dispname, docname, anchor = props[tag] + except KeyError: + dispname = tag + docname = anchor = '' + lis.append(( + dispname, 1, docname, + anchor, + '', '', docname or 'unavailable' + )) + objlis = tmap[tag] + for objname in sorted(objlis): + dispname, docname, anchor = cells[objname] + lis.append(( + dispname, 2, docname, + anchor, + '', '', docname + )) + ret = [(k, v) for k, v in sorted(content.items())] + + return (ret, True) + class CommandDomain(Domain): name = 'cmd' label = 'Yosys commands' @@ -419,17 +555,33 @@ class CellDomain(CommandDomain): name = 'cell' label = 'Yosys internal cells' + roles = CommandDomain.roles.copy() + roles.update({ + 'prop': XRefRole() + }) + directives = { 'def': CellNode, + 'defprop': PropNode, 'source': CellSourceNode, 'group': CellGroupNode, } indices = { CellIndex, - TagIndex + PropIndex } + initial_data = { + 'objects': [], # object list + 'obj2prop': {}, # name -> properties + 'obj2title': {}, # name -> title + } + + def get_objects(self): + for obj in self.data['objects']: + yield(obj) + def autoref(name, rawtext: str, text: str, lineno, inliner: Inliner, options=None, content=None): role = 'cell:ref' if text[0] == '$' else 'cmd:ref' @@ -448,8 +600,8 @@ def setup(app: Sphinx): ('cmd-tag', '', 'Tag Index') StandardDomain.initial_data['labels']['cellindex'] =\ ('cell-cell', '', 'Internal cell reference') - StandardDomain.initial_data['labels']['tagindex'] =\ - ('cell-tag', '', 'Tag Index') + StandardDomain.initial_data['labels']['propindex'] =\ + ('cell-prop', '', 'Property Index') StandardDomain.initial_data['anonlabels']['commandindex'] =\ ('cmd-cmd', '') @@ -457,8 +609,8 @@ def setup(app: Sphinx): ('cmd-tag', '') StandardDomain.initial_data['anonlabels']['cellindex'] =\ ('cell-cell', '') - StandardDomain.initial_data['anonlabels']['tagindex'] =\ - ('cell-tag', '') + StandardDomain.initial_data['anonlabels']['propindex'] =\ + ('cell-prop', '') app.add_role('autoref', autoref)