Skip to content

Commit f581485

Browse files
author
Florimond Manca
authored
Clean up table formatting (#30)
1 parent ecc2548 commit f581485

File tree

4 files changed

+85
-46
lines changed

4 files changed

+85
-46
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ The syntax for `mkdocs-click` blocks is the following:
106106
:command: <COMMAND>
107107
:prog_name: <PROG_NAME>
108108
:depth: <DEPTH>
109+
:style: <STYLE>
109110
```
110111

111112
Options:
112113

113-
- `module`: path to the module where the command object is located.
114-
- `command`: name of the command object.
115-
- `prog_name`: _(Optional, default: same as `command`)_ the name to display for the command.
114+
- `module`: Path to the module where the command object is located.
115+
- `command`: Name of the command object.
116+
- `prog_name`: _(Optional, default: same as `command`)_ The name to display for the command.
116117
- `depth`: _(Optional, default: `0`)_ Offset to add when generating headers.
117-
- `style`: _(Optional, default: `plain`)_ style for the options section. The possible choices are `plain` and `table`.
118+
- `style`: _(Optional, default: `plain`)_ Style for the options section. The possible choices are `plain` and `table`.

mkdocs_click/_docs.py

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# (C) Datadog, Inc. 2020-present
22
# All rights reserved
33
# Licensed under the Apache license (see LICENSE)
4-
from typing import Iterable, Iterator, List, Optional, cast
4+
from typing import Iterator, List, Optional, cast
55

66
import click
77

@@ -30,7 +30,7 @@ def _recursively_make_command_docs(
3030
subcommands = _get_sub_commands(ctx.command, ctx)
3131

3232
for command in sorted(subcommands, key=lambda cmd: cmd.name):
33-
yield from _recursively_make_command_docs(command.name, command, parent=ctx, level=level + 1)
33+
yield from _recursively_make_command_docs(command.name, command, parent=ctx, level=level + 1, style=style)
3434

3535

3636
def _get_sub_commands(command: click.Command, ctx: click.Context) -> List[click.Command]:
@@ -135,42 +135,80 @@ def _make_plain_options(ctx: click.Context) -> Iterator[str]:
135135
yield ""
136136

137137

138-
def _make_table_options(ctx: click.Context) -> Iterator[str]:
139-
"""Create the table style options description."""
138+
# Unicode "Vertical Line" character (U+007C), HTML-compatible.
139+
# "\|" (escaped pipe) would work, too, but linters don't like it in literals.
140+
# https://stackoverflow.com/questions/23723396/how-to-show-the-pipe-symbol-in-markdown-table
141+
_HTML_PIPE = "&#x7C;"
142+
143+
144+
def _format_table_option_type(option: click.Option) -> str:
145+
typename = option.type.name
146+
147+
# TODO: remove "# type: ignore" comments once https://github.com/python/typeshed/pull/4813 gets merged and released.
140148

141-
def backquote(opts: Iterable[str]) -> List[str]:
142-
return [f"`{opt}`" for opt in opts]
143-
144-
def format_possible_value(opt: click.Option) -> str:
145-
param_type = opt.type
146-
display_name = param_type.name
147-
148-
# TODO: remove type-ignore comments once python/typeshed#4813 gets merged.
149-
if isinstance(param_type, click.Choice):
150-
return f"{display_name} ({' &#x7C; '.join(backquote(param_type.choices))})"
151-
elif isinstance(param_type, click.DateTime):
152-
return f"{display_name} ({' &#x7C; '.join(backquote(param_type.formats))})" # type: ignore[attr-defined]
153-
elif isinstance(param_type, (click.IntRange, click.FloatRange)):
154-
if param_type.min is not None and param_type.max is not None: # type: ignore[union-attr]
155-
return f"{display_name} (between `{param_type.min}` and `{param_type.max}`)" # type: ignore[union-attr]
156-
elif param_type.min is not None: # type: ignore[union-attr]
157-
return f"{display_name} (`{param_type.min}` and above)" # type: ignore[union-attr]
158-
else:
159-
return f"{display_name} (`{param_type.max}` and below)" # type: ignore[union-attr]
149+
if isinstance(option.type, click.Choice):
150+
# @click.option(..., type=click.Choice(["A", "B", "C"]))
151+
# -> choices (`A` | `B` | `C`)
152+
choices = f" {_HTML_PIPE} ".join(f"`{choice}`" for choice in option.type.choices)
153+
return f"{typename} ({choices})"
154+
155+
if isinstance(option.type, click.DateTime):
156+
# @click.option(..., type=click.DateTime(["A", "B", "C"]))
157+
# -> datetime (`%Y-%m-%d` | `%Y-%m-%dT%H:%M:%S` | `%Y-%m-%d %H:%M:%S`)
158+
formats = f" {_HTML_PIPE} ".join(f"`{fmt}`" for fmt in option.type.formats) # type: ignore[attr-defined]
159+
return f"{typename} ({formats})"
160+
161+
if isinstance(option.type, (click.IntRange, click.FloatRange)):
162+
if option.type.min is not None and option.type.max is not None: # type: ignore[union-attr]
163+
# @click.option(..., type=click.IntRange(min=0, max=10))
164+
# -> integer range (between `0` and `10`)
165+
return f"{typename} (between `{option.type.min}` and `{option.type.max}`)" # type: ignore[union-attr]
166+
elif option.type.min is not None: # type: ignore[union-attr]
167+
# @click.option(..., type=click.IntRange(min=0))
168+
# -> integer range (`0` and above)
169+
return f"{typename} (`{option.type.min}` and above)" # type: ignore[union-attr]
160170
else:
161-
return display_name
171+
# @click.option(..., type=click.IntRange(max=10))
172+
# -> integer range (`10` and below)
173+
return f"{typename} (`{option.type.max}` and below)" # type: ignore[union-attr]
174+
175+
# -> "boolean", "text", etc.
176+
return typename
177+
178+
179+
def _format_table_option_row(option: click.Option) -> str:
180+
# Example: @click.option("-V, --version/--show-version", is_flag=True, help="Show version info.")
181+
182+
# -> "`-V`, `--version`"
183+
names = ", ".join(f"`{opt}`" for opt in option.opts)
184+
185+
if option.secondary_opts:
186+
# -> "`-V`, `--version` / `--show-info`"
187+
names += " / "
188+
names += ", ".join(f"`{opt}`" for opt in option.secondary_opts)
189+
190+
# -> "boolean"
191+
value_type = _format_table_option_type(option)
192+
193+
# -> "Show version info."
194+
description = option.help if option.help is not None else "N/A"
195+
196+
# -> `False`
197+
default = f"`{option.default}`" if option.default is not None else "_required_"
198+
199+
# -> "| `-V`, `--version` / `--show-version` | boolean | Show version info. | `False` |"
200+
return f"| {names} | {value_type} | {description} | {default} |"
201+
202+
203+
def _make_table_options(ctx: click.Context) -> Iterator[str]:
204+
"""Create the table style options description."""
162205

163-
params = [param for param in ctx.command.get_params(ctx) if isinstance(param, click.Option)]
206+
options = [param for param in ctx.command.get_params(ctx) if isinstance(param, click.Option)]
207+
option_rows = [_format_table_option_row(option) for option in options]
164208

165209
yield "Options:"
166210
yield ""
167211
yield "| Name | Type | Description | Default |"
168-
yield "| ------ | ---- | ----------- | ------- |"
169-
for param in params:
170-
names = ", ".join(backquote(param.opts))
171-
names_negation = f" / {', '.join(backquote(param.secondary_opts))}" if param.secondary_opts != [] else ""
172-
value_type = format_possible_value(param)
173-
description = param.help if param.help is not None else "N/A"
174-
default = f"`{param.default}`" if param.default is not None else "_required_"
175-
yield f"| {names}{names_negation} | {value_type} | {description} | {default} |"
212+
yield "| ---- | ---- | ----------- | ------- |"
213+
yield from option_rows
176214
yield ""

mkdocs_click/_extension.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def replace_command_docs(**options: Any) -> Iterator[str]:
2121
command = options["command"]
2222
prog_name = options.get("prog_name", command)
2323
depth = int(options.get("depth", 0))
24-
style = options.get("option-style", "plain")
24+
style = options.get("style", "plain")
2525

2626
command_obj = load_command(module, command)
2727

tests/unit/test_docs.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def hello_table():
8888
Options:
8989
9090
| Name | Type | Description | Default |
91-
| ------ | ---- | ----------- | ------- |
91+
| ---- | ---- | ----------- | ------- |
9292
| `-d`, `--debug` | text | Include debug output | _required_ |
9393
| `--choice` | choice (`foo` &#x7C; `bar`) | N/A | `foo` |
9494
| `--date` | datetime (`%Y-%m-%d`) | N/A | _required_ |
@@ -107,11 +107,11 @@ def test_make_command_docs_table():
107107

108108

109109
@click.command()
110-
def hello_only_help():
110+
def hello_minimal():
111111
"""Hello, world!"""
112112

113113

114-
HELLO_ONLY_HELP_EXPECTED = dedent(
114+
HELLO_TABLE_MINIMAL_EXPECTED = dedent(
115115
"""
116116
# hello
117117
@@ -120,21 +120,21 @@ def hello_only_help():
120120
Usage:
121121
122122
```
123-
hello-only-help [OPTIONS]
123+
hello-minimal [OPTIONS]
124124
```
125125
126126
Options:
127127
128128
| Name | Type | Description | Default |
129-
| ------ | ---- | ----------- | ------- |
129+
| ---- | ---- | ----------- | ------- |
130130
| `--help` | boolean | Show this message and exit. | `False` |
131131
"""
132132
).strip()
133133

134134

135-
def test_make_command_docs_only_help():
136-
output = "\n".join(make_command_docs("hello", hello_only_help, style="table")).strip()
137-
assert output == HELLO_ONLY_HELP_EXPECTED
135+
def test_make_command_docs_table_minimale():
136+
output = "\n".join(make_command_docs("hello", hello_minimal, style="table")).strip()
137+
assert output == HELLO_TABLE_MINIMAL_EXPECTED
138138

139139

140140
class MultiCLI(click.MultiCommand):

0 commit comments

Comments
 (0)