From ebd6d5f85bcc2a4e2df826dbb3faf96dfbd983c7 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:28:48 +1300 Subject: [PATCH] cmdref: Drop optiongroups Linking to optiongroups doesn't add *that* much, and is kind of a pain; meanwhile having the optiongroups adds an extra level of indentation. Instead of options needing to be in an option group, they instead go in either the root node or nested in a usage node. Putting them in a usage node allows for more-or-less the previous behaviour but without making it the default. --- docs/source/cmd/index_internal.rst | 14 +-- docs/util/cmd_documenter.py | 4 +- docs/util/custom_directives.py | 171 +++++++++++++---------------- kernel/log_help.cc | 29 +++-- kernel/log_help.h | 6 +- kernel/register.cc | 7 +- passes/cmds/chformal.cc | 34 +++--- 7 files changed, 117 insertions(+), 148 deletions(-) diff --git a/docs/source/cmd/index_internal.rst b/docs/source/cmd/index_internal.rst index 597d35639..a077dc2a4 100644 --- a/docs/source/cmd/index_internal.rst +++ b/docs/source/cmd/index_internal.rst @@ -53,21 +53,13 @@ The ``formatted_help()`` method dash (``-``); takes an optional second argument which adds a paragraph node as a means of description - + ``ContentListing::open_optiongroup`` - - * each option must be in an optiongroup - * optional name argument, which will be rendered in (RST) output - + + ``ContentListing::open_usage`` creates and returns a new usage node, can be + used to e.g. add text/options specific to a given usage of the command + ``ContentListing::open_option`` creates and returns a new option node, can be used to e.g. add multiple paragraphs to an option's description + paragraphs are treated as raw RST, allowing for inline formatting and references as if it were written in the RST file itself -.. todo:: Support anonymous optiongroup - - If an option is added to the root node it should add the option to the last - child of the root, making a new child if the last child is not an optiongroup - .. literalinclude:: /generated/chformal.cc :language: c++ :start-at: bool formatted_help() @@ -88,7 +80,7 @@ Dumping command help to json * if a line is indented and starts with a dash (``-``), it is parsed as an option * anything else is parsed as a codeblock and added to either the root node - or the current option/optiongroup depending on the indentation + or the current option depending on the indentation + dictionary of command name to ``ContentListing`` diff --git a/docs/util/cmd_documenter.py b/docs/util/cmd_documenter.py index 39d86cc02..9347d8ffd 100644 --- a/docs/util/cmd_documenter.py +++ b/docs/util/cmd_documenter.py @@ -365,14 +365,14 @@ class YosysCmdDocumenter(YosysCmdGroupDocumenter): def render(content_list: YosysCmdContentListing, indent: int=0): content_source = content_list.source_file or source_name indent_str = ' '*indent - if content_list.type in ['usage', 'optiongroup']: + if content_list.type == 'usage': if content_list.body: self.add_line(f'{indent_str}.. {domain}:{content_list.type}:: {self.name}::{content_list.body}', content_source) else: self.add_line(f'{indent_str}.. {domain}:{content_list.type}:: {self.name}::', content_source) self.add_line(f'{indent_str} :noindex:', source_name) self.add_line('', source_name) - elif content_list.type in ['option']: + elif content_list.type == 'option': self.add_line(f'{indent_str}:{content_list.type} {content_list.body}:', content_source) elif content_list.type == 'text': self.add_line(f'{indent_str}{content_list.body}', content_source) diff --git a/docs/util/custom_directives.py b/docs/util/custom_directives.py index 58e5b37fd..e8c400047 100644 --- a/docs/util/custom_directives.py +++ b/docs/util/custom_directives.py @@ -58,101 +58,12 @@ class TocNode(ObjectDescription): return '.'.join(parents + [name]) return '' -class CommandNode(TocNode): - """A custom node that describes a command.""" - - name = 'cmd' - required_arguments = 1 - - option_spec = TocNode.option_spec.copy() - option_spec.update({ - 'title': directives.unchanged, - 'tags': directives.unchanged - }) +class NodeWithOptions(TocNode): + """A custom node with options.""" doc_field_types = [ GroupedField('opts', label='Options', names=('option', 'options', 'opt', 'opts')), ] - - def handle_signature(self, sig, signode: addnodes.desc_signature): - signode['fullname'] = sig - signode += addnodes.desc_addname(text="yosys> help ") - signode += addnodes.desc_name(text=sig) - return signode['fullname'] - - def add_target_and_index(self, name_cls, sig, signode): - 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'] - tagmap[name] = list(self.options.get('tags', '').split(' ')) - title = self.options.get('title', sig) - 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, - type(self).name, - self.env.docname, - idx, - 0)) - -class CommandUsageNode(TocNode): - """A custom node that describes command usages""" - - name = 'cmdusage' - - option_spec = TocNode.option_spec - option_spec.update({ - 'usage': directives.unchanged, - }) - - doc_field_types = [ - GroupedField('opts', label='Options', names=('option', 'options', 'opt', 'opts')), - ] - - def handle_signature(self, sig: str, signode: addnodes.desc_signature): - parts = sig.split('::') - if len(parts) > 2: parts.pop(0) - use = parts[-1] - signode['fullname'] = '::'.join(parts) - usage = self.options.get('usage', use) - if usage: - signode['tocname'] = usage - signode += addnodes.desc_name(text=usage) - 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 CommandOptionGroupNode(CommandUsageNode): - """A custom node that describes a group of related options""" - - name = 'cmdoptiongroup' - - option_spec = CommandUsageNode.option_spec - - doc_field_types = [ - Field('opt', ('option',), label='', rolename='option') - ] def transform_content(self, contentnode: addnodes.desc_content) -> None: """hack `:option -thing: desc` into a proper option list with yoscrypt highlighting""" @@ -186,6 +97,83 @@ class CommandOptionGroupNode(CommandUsageNode): newchildren.append(newnode) contentnode.children = newchildren +class CommandNode(NodeWithOptions): + """A custom node that describes a command.""" + + name = 'cmd' + required_arguments = 1 + + option_spec = NodeWithOptions.option_spec.copy() + option_spec.update({ + 'title': directives.unchanged, + 'tags': directives.unchanged + }) + + def handle_signature(self, sig, signode: addnodes.desc_signature): + signode['fullname'] = sig + signode += addnodes.desc_addname(text="yosys> help ") + signode += addnodes.desc_name(text=sig) + return signode['fullname'] + + def add_target_and_index(self, name_cls, sig, signode): + 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'] + tagmap[name] = list(self.options.get('tags', '').split(' ')) + title = self.options.get('title', sig) + 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, + type(self).name, + self.env.docname, + idx, + 0)) + +class CommandUsageNode(NodeWithOptions): + """A custom node that describes command usages""" + + name = 'cmdusage' + + option_spec = NodeWithOptions.option_spec + option_spec.update({ + 'usage': directives.unchanged, + }) + + def handle_signature(self, sig: str, signode: addnodes.desc_signature): + parts = sig.split('::') + if len(parts) > 2: parts.pop(0) + use = parts[-1] + signode['fullname'] = '::'.join(parts) + usage = self.options.get('usage', use) + if usage: + signode['tocname'] = usage + signode += addnodes.desc_name(text=usage) + 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 PropNode(TocNode): name = 'prop' fieldname = 'props' @@ -621,7 +609,6 @@ class CommandDomain(Domain): directives = { 'def': CommandNode, 'usage': CommandUsageNode, - 'optiongroup': CommandOptionGroupNode, } indices = { diff --git a/kernel/log_help.cc b/kernel/log_help.cc index 9c1687910..45228b024 100644 --- a/kernel/log_help.cc +++ b/kernel/log_help.cc @@ -34,11 +34,11 @@ Json ContentListing::to_json() { return object; } -void ContentListing::usage(const string &usage, +void ContentListing::usage(const string &text, const source_location location) { log_assert(type.compare("root") == 0); - add_content("usage", usage, location); + add_content("usage", text, location); } void ContentListing::option(const string &text, const string &description, @@ -62,19 +62,17 @@ void ContentListing::paragraph(const string &text, add_content("text", text, location); } -ContentListing* ContentListing::open_optiongroup(const string &name, +ContentListing* ContentListing::open_usage(const string &text, const source_location location) { - log_assert(type.compare("root") == 0); - auto optiongroup = new ContentListing("optiongroup", name, location); - add_content(optiongroup); - return optiongroup; + usage(text, location); + return back(); } ContentListing* ContentListing::open_option(const string &text, const source_location location) { - log_assert(type.compare("optiongroup") == 0); + log_assert(type.compare("root") == 0 || type.compare("usage") == 0); auto option = new ContentListing("option", text, location); add_content(option); return option; @@ -138,16 +136,15 @@ void PrettyHelp::log_help() for (auto content : _root_listing.get_content()) { if (content->type.compare("usage") == 0) { log_pass_str(content->body, 1, true); - } else if (content->type.compare("optiongroup") == 0) { - for (auto option : content->get_content()) { - log_pass_str(option->body, 1); - for (auto text : option->get_content()) { - log_pass_str(text->body, 2); - log("\n"); - } + log("\n"); + } else if (content->type.compare("option") == 0) { + log_pass_str(content->body, 1); + for (auto text : content->get_content()) { + log_pass_str(text->body, 2); + log("\n"); } } else { - log_pass_str(content->body, 0, true); + log_pass_str(content->body, 0); log("\n"); } } diff --git a/kernel/log_help.h b/kernel/log_help.h index 60f378e00..0a40fc531 100644 --- a/kernel/log_help.h +++ b/kernel/log_help.h @@ -68,7 +68,7 @@ public: } void usage( - const string &usage, + const string &text, const source_location location = source_location::current() ); void option( @@ -86,8 +86,8 @@ public: const source_location location = source_location::current() ); - ContentListing* open_optiongroup( - const string &name = "", + ContentListing* open_usage( + const string &text, const source_location location = source_location::current() ); ContentListing* open_option( diff --git a/kernel/register.cc b/kernel/register.cc index df3e1d453..411280058 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -947,13 +947,8 @@ struct HelpPass : public Pass { current_listing->codeblock(current_buffer, "none", null_source); current_buffer = ""; } - if (current_state == PUState_options || current_state == PUState_optionbody) { - current_listing = root_listing->back(); - } else { - current_listing = root_listing->open_optiongroup("", null_source); - } current_state = PUState_options; - current_listing = current_listing->open_option(stripped_line, null_source); + current_listing = root_listing->open_option(stripped_line, null_source); def_strip_count = first_pos; } else { if (current_state == PUState_options) { diff --git a/passes/cmds/chformal.cc b/passes/cmds/chformal.cc index b82b7d89b..1903dceda 100644 --- a/passes/cmds/chformal.cc +++ b/passes/cmds/chformal.cc @@ -86,29 +86,27 @@ struct ChformalPass : public Pass { "given, the command will operate on all constraint types:" ); - auto types_group = content_root->open_optiongroup("[types]"); - types_group->option("-assert", "`$assert` cells, representing ``assert(...)`` constraints"); - types_group->option("-assume", "`$assume` cells, representing ``assume(...)`` constraints"); - types_group->option("-live", "`$live` cells, representing ``assert(s_eventually ...)``"); - types_group->option("-fair", "`$fair` cells, representing ``assume(s_eventually ...)``"); - types_group->option("-cover", "`$cover` cells, representing ``cover()`` statements"); - types_group->paragraph( + content_root->option("-assert", "`$assert` cells, representing ``assert(...)`` constraints"); + content_root->option("-assume", "`$assume` cells, representing ``assume(...)`` constraints"); + content_root->option("-live", "`$live` cells, representing ``assert(s_eventually ...)``"); + content_root->option("-fair", "`$fair` cells, representing ``assume(s_eventually ...)``"); + content_root->option("-cover", "`$cover` cells, representing ``cover()`` statements"); + content_root->paragraph( "Additionally chformal will operate on `$check` cells corresponding to the " "selected constraint types." ); content_root->paragraph("Exactly one of the following modes must be specified:"); - auto modes_group = content_root->open_optiongroup("[mode]"); - modes_group->option("-remove", "remove the cells and thus constraints from the design"); - modes_group->option("-early", + content_root->option("-remove", "remove the cells and thus constraints from the design"); + content_root->option("-early", "bypass FFs that only delay the activation of a constraint. When inputs " "of the bypassed FFs do not remain stable between clock edges, this may " "result in unexpected behavior." ); - modes_group->option("-delay ", "delay activation of the constraint by clock cycles"); - modes_group->option("-skip ", "ignore activation of the constraint in the first clock cycles"); - auto cover_option = modes_group->open_option("-coverenable"); + content_root->option("-delay ", "delay activation of the constraint by clock cycles"); + content_root->option("-skip ", "ignore activation of the constraint in the first clock cycles"); + auto cover_option = content_root->open_option("-coverenable"); cover_option->paragraph( "add cover statements for the enable signals of the constraints" ); @@ -118,11 +116,11 @@ struct ChformalPass : public Pass { "reachable SVA statement corresponds to an active enable signal." ); #endif - modes_group->option("-assert2assume"); - modes_group->option("-assume2assert"); - modes_group->option("-live2fair"); - modes_group->option("-fair2live", "change the roles of cells as indicated. these options can be combined"); - modes_group->option("-lower", + content_root->option("-assert2assume"); + content_root->option("-assume2assert"); + content_root->option("-live2fair"); + content_root->option("-fair2live", "change the roles of cells as indicated. these options can be combined"); + content_root->option("-lower", "convert each $check cell into an $assert, $assume, $live, $fair or " "$cover cell. If the $check cell contains a message, also produce a " "$print cell."