|
1 | 1 | """Code to use internally, for documentation.""" |
2 | 2 | from __future__ import annotations |
3 | 3 |
|
| 4 | +import io |
4 | 5 | from typing import Sequence, Union |
5 | 6 |
|
6 | 7 | from docutils import nodes |
| 8 | +from docutils.frontend import OptionParser |
7 | 9 | from docutils.parsers.rst import directives |
| 10 | +from sphinx.directives import other |
| 11 | +from sphinx.util import logging |
8 | 12 | from sphinx.util.docutils import SphinxDirective |
9 | 13 | from typing_extensions import get_args, get_origin |
10 | 14 |
|
11 | 15 | from .config.main import MdParserConfig |
| 16 | +from .parsers.docutils_ import Parser as DocutilsParser |
| 17 | + |
| 18 | +logger = logging.getLogger(__name__) |
12 | 19 |
|
13 | 20 |
|
14 | 21 | class _ConfigBase(SphinxDirective): |
@@ -117,16 +124,75 @@ class DocutilsCliHelpDirective(SphinxDirective): |
117 | 124 |
|
118 | 125 | def run(self): |
119 | 126 | """Run the directive.""" |
120 | | - import io |
121 | | - |
122 | | - from docutils import nodes |
123 | | - from docutils.frontend import OptionParser |
124 | | - |
125 | | - from myst_parser.parsers.docutils_ import Parser as DocutilsParser |
126 | | - |
127 | 127 | stream = io.StringIO() |
128 | 128 | OptionParser( |
129 | 129 | components=(DocutilsParser,), |
130 | 130 | usage="myst-docutils-<writer> [options] [<source> [<destination>]]", |
131 | 131 | ).print_help(stream) |
132 | 132 | return [nodes.literal_block("", stream.getvalue())] |
| 133 | + |
| 134 | + |
| 135 | +class DirectiveDoc(SphinxDirective): |
| 136 | + """Load and document a directive.""" |
| 137 | + |
| 138 | + required_arguments = 1 # name of the directive |
| 139 | + has_content = True |
| 140 | + |
| 141 | + def run(self): |
| 142 | + """Run the directive.""" |
| 143 | + name = self.arguments[0] |
| 144 | + # load the directive class |
| 145 | + klass, _ = directives.directive( |
| 146 | + name, self.state.memo.language, self.state.document |
| 147 | + ) |
| 148 | + if klass is None: |
| 149 | + logger.warning(f"Directive {name} not found.", line=self.lineno) |
| 150 | + return [] |
| 151 | + content = " ".join(self.content) |
| 152 | + text = f"""\ |
| 153 | +:Name: `{name}` |
| 154 | +:Description: {content} |
| 155 | +:Arguments: {klass.required_arguments} required, {klass.optional_arguments} optional |
| 156 | +:Content: {'yes' if klass.has_content else 'no'} |
| 157 | +:Options: |
| 158 | +""" |
| 159 | + if klass.option_spec: |
| 160 | + text += " name | type\n -----|------\n" |
| 161 | + for key, func in klass.option_spec.items(): |
| 162 | + text += f" {key} | {convert_opt(name, func)}\n" |
| 163 | + node = nodes.Element() |
| 164 | + self.state.nested_parse(text.splitlines(), 0, node) |
| 165 | + return node.children |
| 166 | + |
| 167 | + |
| 168 | +def convert_opt(name, func): |
| 169 | + """Convert an option function to a string.""" |
| 170 | + if func is directives.flag: |
| 171 | + return "flag" |
| 172 | + if func is directives.unchanged: |
| 173 | + return "text" |
| 174 | + if func is directives.unchanged_required: |
| 175 | + return "text" |
| 176 | + if func is directives.class_option: |
| 177 | + return "space-delimited list" |
| 178 | + if func is directives.uri: |
| 179 | + return "URI" |
| 180 | + if func is directives.path: |
| 181 | + return "path" |
| 182 | + if func is int: |
| 183 | + return "integer" |
| 184 | + if func is directives.positive_int: |
| 185 | + return "integer (positive)" |
| 186 | + if func is directives.nonnegative_int: |
| 187 | + return "integer (non-negative)" |
| 188 | + if func is directives.positive_int_list: |
| 189 | + return "space/comma-delimited list of integers (positive)" |
| 190 | + if func is directives.percentage: |
| 191 | + return "percentage" |
| 192 | + if func is directives.length_or_unitless: |
| 193 | + return "length or unitless" |
| 194 | + if func is directives.length_or_percentage_or_unitless: |
| 195 | + return "length, percentage or unitless" |
| 196 | + if func is other.int_or_nothing: |
| 197 | + return "integer" |
| 198 | + return "" |
0 commit comments