Skip to content

Instantly share code, notes, and snippets.

@tigattack
Last active November 12, 2024 18:19
Show Gist options
  • Save tigattack/3eb895c5416d9ccc24a8236ec6eb4184 to your computer and use it in GitHub Desktop.
Save tigattack/3eb895c5416d9ccc24a8236ec6eb4184 to your computer and use it in GitHub Desktop.
Generate Markdown docs from Ansible argument_specs.yml

This script will generate Markdown sections from the contents of an Ansible role's meta/argument_specs.yml in the following style:

option_name

Type Default
string default value here

Raw, this looks like:

### `option_name`

| Type   | Default              |
|--------|----------------------|
| string | `default value here` |

For example, given the following meta/argument_specs.yml:

argument_specs:
  main:
    ...
    options:

      myapp_int:
        type: "int"
        required: false
        default: 42
        description:
          - "The integer value, defaulting to 42."
          - "This is a second paragraph."

      myapp_str:
        type: "str"
        required: true
        description: "The string value"

      myapp_list:
        type: "list"
        elements: "str"
        required: true
        description: "A list of string values."
        version_added: 1.3.0

The following Markdown will be generated:

myapp_int

Type Default
int 42

The integer value, defaulting to 42.

This is a second paragraph.

myapp_str

Type Default
string None

The string value

myapp_list

Type Default
list[str] None

A list of string values.


Raw, this looks like:

### `myapp_int`

| Type | Default |
|------|---------|
| int  | `42`    |

The integer value, defaulting to 42.

This is a second paragraph.


### `myapp_str`

| Type   | Default |
|--------|---------|
| string | None    |

The string value


### `myapp_list`

| Type      | Default |
|-----------|---------|
| list[str] | None    |

A list of string values.
import yaml
with open("meta/argument_specs.yml", "r") as f:
argspec = yaml.load(f, Loader=yaml.Loader)
argspec_options = argspec["argument_specs"]["main"]["options"]
md: list[str] = []
for k, v in argspec_options.items():
type_str = "string" if v["type"] == "str" else v["type"]
if v.get("elements"):
type_str = f"{type_str}[{v['elements']}]"
default_str = v.get("default")
description_extra = ""
# Length of type value
type_len = len(type_str)
# Lengths of header values
type_header_text = "Type" + " " * (type_len - 4) if type_len > 4 else "Type"
type_header_row = (
"-" * (len(type_header_text) + 2) if len(type_header_text) > 5 else "------"
)
# Body value spacing
type_body_spacing = " " * (len(type_header_text) - type_len)
if default_str is not None:
# Format default value and wrap in backticks
match default_str:
case bool():
default_str = str(f"`{default_str}`").lower()
case str():
if len(default_str) > 0:
default_str = f"`{default_str}`"
else:
default_str = ""
case int():
default_str = str(f"`{default_str}`")
case dict():
if len(default_str.items()) > 0:
description_extra = "**Default:**"
for k, v in default_str.items():
description_extra += f"\n- `{k}`: `{v}`"
else:
default_str = "`{}`"
case list():
if len(default_str) > 0:
for i, v in enumerate(default_str):
default_str[i] = f"`{v}`"
default_str = ", ".join(default_str)
else:
default_str = "`{}`"
# Length of default value
default_len = len(str(default_str))
# Lengths of header values
default_header_text = (
"Default" + " " * (default_len - 7) if default_len > 7 else "Default"
)
default_header_row = "-" * (default_len + 2) if default_len > 7 else "---------"
# Body values spacing
default_body_spacing = " " * (len(default_header_text) - default_len)
else:
default_str = str(default_str)
default_header_text = "Default"
default_header_row = "-" * (len(default_header_text) + 2)
default_body_spacing = " " * (len(default_header_text) - len(default_str))
description = v.get("description")
description_fmt = (
description if isinstance(description, str) else "\n\n".join(description)
)
if description_extra:
description_fmt += f"\n\n{description_extra}"
choices = v.get("choices")
if choices:
choices = [str(f"`{c}`") for c in choices]
description_fmt += f"\n\nMust be one of: {", ".join(choices)}."
md.append(f"""
### `{k}`
| {type_header_text} | {default_header_text} |
|{type_header_row}|{default_header_row}|
| {type_str}{type_body_spacing} | {default_str}{default_body_spacing} |
{description_fmt}
""")
print("\n".join(md))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment