|  | 
| 4 | 4 | # list see the documentation: | 
| 5 | 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html | 
| 6 | 6 | 
 | 
|  | 7 | +from argparse import _StoreTrueAction | 
|  | 8 | +from io import StringIO | 
| 7 | 9 | from pathlib import Path | 
|  | 10 | +from typing import Optional | 
|  | 11 | +import docutils | 
| 8 | 12 | from sphinx.application import Sphinx | 
|  | 13 | +from sphinx.util.docutils import SphinxRole | 
|  | 14 | +from sphinx_immaterial.inline_icons import load_svg_into_builder_env | 
| 9 | 15 | from clang_tools.main import get_parser | 
| 10 | 16 | 
 | 
| 11 | 17 | # -- Path setup -------------------------------------------------------------- | 
|  | 
| 111 | 117 | # pylint: disable=protected-access | 
| 112 | 118 | 
 | 
| 113 | 119 | 
 | 
|  | 120 | +class CliBadge(SphinxRole): | 
|  | 121 | + badge_type: str | 
|  | 122 | + badge_icon: Optional[str] = None | 
|  | 123 | + href: Optional[str] = None | 
|  | 124 | + href_title: Optional[str] = None | 
|  | 125 | + | 
|  | 126 | + def run(self): | 
|  | 127 | + is_linked = "" | 
|  | 128 | + if self.href is not None and self.href_title is not None: | 
|  | 129 | + is_linked = ( | 
|  | 130 | + f'<a href="{self.href}{self.text}" ' + f'title="{self.href_title}">' | 
|  | 131 | + ) | 
|  | 132 | + head = '<span class="mdx-badge__icon">' | 
|  | 133 | + if not self.badge_icon: | 
|  | 134 | + head += self.badge_type.title() | 
|  | 135 | + else: | 
|  | 136 | + head += is_linked | 
|  | 137 | + head += ( | 
|  | 138 | + f'<span class="md-icon si-icon-inline {self.badge_icon}"></span></a>' | 
|  | 139 | + ) | 
|  | 140 | + head += "</span>" | 
|  | 141 | + header = docutils.nodes.raw( | 
|  | 142 | + self.rawtext, | 
|  | 143 | + f'<span class="mdx-badge">{head}<span class="mdx-badge__text">' | 
|  | 144 | + + is_linked | 
|  | 145 | + + (self.text if self.badge_type in ["version", "switch"] else ""), | 
|  | 146 | + format="html", | 
|  | 147 | + ) | 
|  | 148 | + if self.badge_type not in ["version", "switch"]: | 
|  | 149 | + code, sys_msgs = docutils.parsers.rst.roles.code_role( | 
|  | 150 | + role="code", | 
|  | 151 | + rawtext=self.rawtext, | 
|  | 152 | + text=self.text, | 
|  | 153 | + lineno=self.lineno, | 
|  | 154 | + inliner=self.inliner, | 
|  | 155 | + options={"language": "text", "classes": ["highlight"]}, | 
|  | 156 | + content=self.content, | 
|  | 157 | + ) | 
|  | 158 | + else: | 
|  | 159 | + code, sys_msgs = ([], []) | 
|  | 160 | + tail = "</span></span>" | 
|  | 161 | + if self.href is not None and self.href_title is not None: | 
|  | 162 | + tail = "</a>" + tail | 
|  | 163 | + trailer = docutils.nodes.raw(self.rawtext, tail, format="html") | 
|  | 164 | + return ([header, *code, trailer], sys_msgs) | 
|  | 165 | + | 
|  | 166 | + | 
|  | 167 | +class CliBadgeVersion(CliBadge): | 
|  | 168 | + badge_type = "version" | 
|  | 169 | + href = "https://github.com/cpp-linter/clang-tools-pip/releases/v" | 
|  | 170 | + href_title = "Minimum Version" | 
|  | 171 | + | 
|  | 172 | + def run(self): | 
|  | 173 | + self.badge_icon = load_svg_into_builder_env( | 
|  | 174 | + self.env.app.builder, "material/tag-outline" | 
|  | 175 | + ) | 
|  | 176 | + return super().run() | 
|  | 177 | + | 
|  | 178 | + | 
|  | 179 | +class CliBadgeDefault(CliBadge): | 
|  | 180 | + badge_type = "Default" | 
|  | 181 | + | 
|  | 182 | + | 
|  | 183 | +class CliBadgeSwitch(CliBadge): | 
|  | 184 | + badge_type = "switch" | 
|  | 185 | + | 
|  | 186 | + def run(self): | 
|  | 187 | + self.badge_icon = load_svg_into_builder_env( | 
|  | 188 | + self.env.app.builder, "material/toggle-switch" | 
|  | 189 | + ) | 
|  | 190 | + return super().run() | 
|  | 191 | + | 
|  | 192 | + | 
|  | 193 | +REQUIRED_VERSIONS = { | 
|  | 194 | + "0.1.0": ["install"], | 
|  | 195 | + "0.2.0": ["directory"], | 
|  | 196 | + "0.3.0": ["overwrite"], | 
|  | 197 | + "0.5.0": ["no_progress_bar", "uninstall"], | 
|  | 198 | + "0.11.0": ["tool"], | 
|  | 199 | +} | 
|  | 200 | + | 
|  | 201 | + | 
| 114 | 202 | def setup(app: Sphinx): | 
| 115 | 203 |  """Generate a doc from the executable script's ``--help`` output.""" | 
| 116 |  | - parser = get_parser() | 
| 117 |  | - # print(parser.format_help()) | 
| 118 |  | - formatter = parser._get_formatter() | 
| 119 |  | - doc = "Command Line Interface Options\n==============================\n\n" | 
| 120 |  | - for arg in parser._actions: | 
| 121 |  | - doc += f"\n.. option:: {formatter._format_action_invocation(arg)}\n\n" | 
| 122 |  | - if arg.default != "==SUPPRESS==": | 
| 123 |  | - doc += f" :Default: ``{repr(arg.default)}``\n\n" | 
| 124 |  | - description = ( | 
| 125 |  | - "" if arg.help is None else " %s\n" % (arg.help.replace("\n", "\n ")) | 
| 126 |  | - ) | 
| 127 |  | - doc += description | 
|  | 204 | + app.add_role("badge-version", CliBadgeVersion()) | 
|  | 205 | + app.add_role("badge-default", CliBadgeDefault()) | 
|  | 206 | + app.add_role("badge-switch", CliBadgeSwitch()) | 
|  | 207 | + | 
| 128 | 208 |  cli_doc = Path(app.srcdir, "cli_args.rst") | 
| 129 |  | - cli_doc.write_text(doc, encoding="utf-8") | 
|  | 209 | + with open(cli_doc, mode="w") as doc: | 
|  | 210 | + doc.write("Command Line Interface Options\n==============================\n\n") | 
|  | 211 | + parser = get_parser() | 
|  | 212 | + doc.write(".. code-block:: text\n :caption: Usage\n :class: no-copy\n\n") | 
|  | 213 | + parser.prog = "clang-tools" | 
|  | 214 | + str_buf = StringIO() | 
|  | 215 | + parser.print_usage(str_buf) | 
|  | 216 | + usage = str_buf.getvalue() | 
|  | 217 | + start = usage.find(parser.prog) | 
|  | 218 | + for line in usage.splitlines(): | 
|  | 219 | + doc.write(f" {line[start:]}\n") | 
|  | 220 | + | 
|  | 221 | + args = parser._optionals._actions | 
|  | 222 | + for arg in args: | 
|  | 223 | + aliases = arg.option_strings | 
|  | 224 | + if not aliases or arg.default == "==SUPPRESS==": | 
|  | 225 | + continue | 
|  | 226 | + doc.write("\n.. std:option:: " + ", ".join(aliases) + "\n") | 
|  | 227 | + assert arg.help is not None | 
|  | 228 | + help = arg.help[: arg.help.find("Defaults to")] | 
|  | 229 | + for ver, names in REQUIRED_VERSIONS.items(): | 
|  | 230 | + if arg.dest in names: | 
|  | 231 | + req_ver = ver | 
|  | 232 | + break | 
|  | 233 | + else: | 
|  | 234 | + req_ver = "0.1.0" | 
|  | 235 | + doc.write(f"\n :badge-version:`{req_ver}` ") | 
|  | 236 | + if arg.default: | 
|  | 237 | + default = arg.default | 
|  | 238 | + if isinstance(arg.default, list): | 
|  | 239 | + default = " ".join(arg.default) | 
|  | 240 | + doc.write(f":badge-default:`{default}` ") | 
|  | 241 | + if isinstance(arg, _StoreTrueAction): | 
|  | 242 | + doc.write(":badge-switch:`Accepts no value` ") | 
|  | 243 | + doc.write("\n\n ") | 
|  | 244 | + doc.write("\n ".join(help.splitlines()) + "\n") | 
0 commit comments