3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-06-06 14:13:23 +00:00

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.
This commit is contained in:
Krystine Sherwin 2025-03-21 10:28:48 +13:00
parent a2433ba34b
commit ebd6d5f85b
No known key found for this signature in database
7 changed files with 117 additions and 148 deletions

View file

@ -53,21 +53,13 @@ The ``formatted_help()`` method
dash (``-``); takes an optional second argument which adds a paragraph dash (``-``); takes an optional second argument which adds a paragraph
node as a means of description node as a means of description
+ ``ContentListing::open_optiongroup`` + ``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
* each option must be in an optiongroup
* optional name argument, which will be rendered in (RST) output
+ ``ContentListing::open_option`` creates and returns a new option node, can + ``ContentListing::open_option`` creates and returns a new option node, can
be used to e.g. add multiple paragraphs to an option's description be used to e.g. add multiple paragraphs to an option's description
+ paragraphs are treated as raw RST, allowing for inline formatting and + paragraphs are treated as raw RST, allowing for inline formatting and
references as if it were written in the RST file itself 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 .. literalinclude:: /generated/chformal.cc
:language: c++ :language: c++
:start-at: bool formatted_help() :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 * if a line is indented and starts with a dash (``-``), it is parsed as an
option option
* anything else is parsed as a codeblock and added to either the root node * 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`` + dictionary of command name to ``ContentListing``

View file

@ -365,14 +365,14 @@ class YosysCmdDocumenter(YosysCmdGroupDocumenter):
def render(content_list: YosysCmdContentListing, indent: int=0): def render(content_list: YosysCmdContentListing, indent: int=0):
content_source = content_list.source_file or source_name content_source = content_list.source_file or source_name
indent_str = ' '*indent indent_str = ' '*indent
if content_list.type in ['usage', 'optiongroup']: if content_list.type == 'usage':
if content_list.body: if content_list.body:
self.add_line(f'{indent_str}.. {domain}:{content_list.type}:: {self.name}::{content_list.body}', content_source) self.add_line(f'{indent_str}.. {domain}:{content_list.type}:: {self.name}::{content_list.body}', content_source)
else: else:
self.add_line(f'{indent_str}.. {domain}:{content_list.type}:: {self.name}::', content_source) 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(f'{indent_str} :noindex:', source_name)
self.add_line('', 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) self.add_line(f'{indent_str}:{content_list.type} {content_list.body}:', content_source)
elif content_list.type == 'text': elif content_list.type == 'text':
self.add_line(f'{indent_str}{content_list.body}', content_source) self.add_line(f'{indent_str}{content_list.body}', content_source)

View file

@ -58,102 +58,13 @@ class TocNode(ObjectDescription):
return '.'.join(parents + [name]) return '.'.join(parents + [name])
return '' return ''
class CommandNode(TocNode): class NodeWithOptions(TocNode):
"""A custom node that describes a command.""" """A custom node with options."""
name = 'cmd'
required_arguments = 1
option_spec = TocNode.option_spec.copy()
option_spec.update({
'title': directives.unchanged,
'tags': directives.unchanged
})
doc_field_types = [ doc_field_types = [
GroupedField('opts', label='Options', names=('option', 'options', 'opt', 'opts')), 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: def transform_content(self, contentnode: addnodes.desc_content) -> None:
"""hack `:option -thing: desc` into a proper option list with yoscrypt highlighting""" """hack `:option -thing: desc` into a proper option list with yoscrypt highlighting"""
newchildren = [] newchildren = []
@ -186,6 +97,83 @@ class CommandOptionGroupNode(CommandUsageNode):
newchildren.append(newnode) newchildren.append(newnode)
contentnode.children = newchildren 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): class PropNode(TocNode):
name = 'prop' name = 'prop'
fieldname = 'props' fieldname = 'props'
@ -621,7 +609,6 @@ class CommandDomain(Domain):
directives = { directives = {
'def': CommandNode, 'def': CommandNode,
'usage': CommandUsageNode, 'usage': CommandUsageNode,
'optiongroup': CommandOptionGroupNode,
} }
indices = { indices = {

View file

@ -34,11 +34,11 @@ Json ContentListing::to_json() {
return object; return object;
} }
void ContentListing::usage(const string &usage, void ContentListing::usage(const string &text,
const source_location location) const source_location location)
{ {
log_assert(type.compare("root") == 0); 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, void ContentListing::option(const string &text, const string &description,
@ -62,19 +62,17 @@ void ContentListing::paragraph(const string &text,
add_content("text", text, location); add_content("text", text, location);
} }
ContentListing* ContentListing::open_optiongroup(const string &name, ContentListing* ContentListing::open_usage(const string &text,
const source_location location) const source_location location)
{ {
log_assert(type.compare("root") == 0); usage(text, location);
auto optiongroup = new ContentListing("optiongroup", name, location); return back();
add_content(optiongroup);
return optiongroup;
} }
ContentListing* ContentListing::open_option(const string &text, ContentListing* ContentListing::open_option(const string &text,
const source_location location) 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); auto option = new ContentListing("option", text, location);
add_content(option); add_content(option);
return option; return option;
@ -138,16 +136,15 @@ void PrettyHelp::log_help()
for (auto content : _root_listing.get_content()) { for (auto content : _root_listing.get_content()) {
if (content->type.compare("usage") == 0) { if (content->type.compare("usage") == 0) {
log_pass_str(content->body, 1, true); log_pass_str(content->body, 1, true);
} else if (content->type.compare("optiongroup") == 0) { log("\n");
for (auto option : content->get_content()) { } else if (content->type.compare("option") == 0) {
log_pass_str(option->body, 1); log_pass_str(content->body, 1);
for (auto text : option->get_content()) { for (auto text : content->get_content()) {
log_pass_str(text->body, 2); log_pass_str(text->body, 2);
log("\n"); log("\n");
} }
}
} else { } else {
log_pass_str(content->body, 0, true); log_pass_str(content->body, 0);
log("\n"); log("\n");
} }
} }

View file

@ -68,7 +68,7 @@ public:
} }
void usage( void usage(
const string &usage, const string &text,
const source_location location = source_location::current() const source_location location = source_location::current()
); );
void option( void option(
@ -86,8 +86,8 @@ public:
const source_location location = source_location::current() const source_location location = source_location::current()
); );
ContentListing* open_optiongroup( ContentListing* open_usage(
const string &name = "", const string &text,
const source_location location = source_location::current() const source_location location = source_location::current()
); );
ContentListing* open_option( ContentListing* open_option(

View file

@ -947,13 +947,8 @@ struct HelpPass : public Pass {
current_listing->codeblock(current_buffer, "none", null_source); current_listing->codeblock(current_buffer, "none", null_source);
current_buffer = ""; 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_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; def_strip_count = first_pos;
} else { } else {
if (current_state == PUState_options) { if (current_state == PUState_options) {

View file

@ -86,29 +86,27 @@ struct ChformalPass : public Pass {
"given, the command will operate on all constraint types:" "given, the command will operate on all constraint types:"
); );
auto types_group = content_root->open_optiongroup("[types]"); content_root->option("-assert", "`$assert` cells, representing ``assert(...)`` constraints");
types_group->option("-assert", "`$assert` cells, representing ``assert(...)`` constraints"); content_root->option("-assume", "`$assume` cells, representing ``assume(...)`` constraints");
types_group->option("-assume", "`$assume` cells, representing ``assume(...)`` constraints"); content_root->option("-live", "`$live` cells, representing ``assert(s_eventually ...)``");
types_group->option("-live", "`$live` cells, representing ``assert(s_eventually ...)``"); content_root->option("-fair", "`$fair` cells, representing ``assume(s_eventually ...)``");
types_group->option("-fair", "`$fair` cells, representing ``assume(s_eventually ...)``"); content_root->option("-cover", "`$cover` cells, representing ``cover()`` statements");
types_group->option("-cover", "`$cover` cells, representing ``cover()`` statements"); content_root->paragraph(
types_group->paragraph(
"Additionally chformal will operate on `$check` cells corresponding to the " "Additionally chformal will operate on `$check` cells corresponding to the "
"selected constraint types." "selected constraint types."
); );
content_root->paragraph("Exactly one of the following modes must be specified:"); content_root->paragraph("Exactly one of the following modes must be specified:");
auto modes_group = content_root->open_optiongroup("[mode]"); content_root->option("-remove", "remove the cells and thus constraints from the design");
modes_group->option("-remove", "remove the cells and thus constraints from the design"); content_root->option("-early",
modes_group->option("-early",
"bypass FFs that only delay the activation of a constraint. When inputs " "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 " "of the bypassed FFs do not remain stable between clock edges, this may "
"result in unexpected behavior." "result in unexpected behavior."
); );
modes_group->option("-delay <N>", "delay activation of the constraint by <N> clock cycles"); content_root->option("-delay <N>", "delay activation of the constraint by <N> clock cycles");
modes_group->option("-skip <N>", "ignore activation of the constraint in the first <N> clock cycles"); content_root->option("-skip <N>", "ignore activation of the constraint in the first <N> clock cycles");
auto cover_option = modes_group->open_option("-coverenable"); auto cover_option = content_root->open_option("-coverenable");
cover_option->paragraph( cover_option->paragraph(
"add cover statements for the enable signals of the constraints" "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." "reachable SVA statement corresponds to an active enable signal."
); );
#endif #endif
modes_group->option("-assert2assume"); content_root->option("-assert2assume");
modes_group->option("-assume2assert"); content_root->option("-assume2assert");
modes_group->option("-live2fair"); content_root->option("-live2fair");
modes_group->option("-fair2live", "change the roles of cells as indicated. these options can be combined"); content_root->option("-fair2live", "change the roles of cells as indicated. these options can be combined");
modes_group->option("-lower", content_root->option("-lower",
"convert each $check cell into an $assert, $assume, $live, $fair or " "convert each $check cell into an $assert, $assume, $live, $fair or "
"$cover cell. If the $check cell contains a message, also produce a " "$cover cell. If the $check cell contains a message, also produce a "
"$print cell." "$print cell."