Skip to content

Commit 242b228

Browse files
authored
chore: New docs generation (#68)
* Fix dependencies. Add get_run_context and continue_run features. * Finalizing docs generation and pre-commit cleanup * Constrain docs members * strip test file * generate_docs fixes
1 parent 91a03ab commit 242b228

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+10279
-3321
lines changed
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
---
12
name: Notify Documentation Update
23

34
on:
45
push:
56
branches: [main]
67
paths:
78
- "docs/**"
8-
- "scripts/make_docs.py"
9+
- ".hooks/generate_docs.py"
10+
- ".github/workflows/docs-update.yaml"
911
workflow_dispatch:
1012

1113
jobs:
@@ -28,4 +30,12 @@ jobs:
2830
token: ${{ steps.app-token.outputs.token }}
2931
repository: dreadnode/prod-docs
3032
event-type: code-update
31-
client-payload: '{"repository": "${{ github.repository }}", "ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "product": "strikes", "docs_dir": "docs", "module_dir": "dreadnode"}'
33+
client-payload: |
34+
{
35+
"repository": "${{ github.repository }}",
36+
"ref": "${{ github.ref }}",
37+
"sha": "${{ github.sha }}",
38+
"source_dir": "docs",
39+
"target_dir": "strikes",
40+
"nav_target": "Documentation/Strikes"
41+
}

.github/workflows/publish.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
name: Build and Publish
23

34
on:
@@ -46,4 +47,4 @@ jobs:
4647
run: poetry build
4748

4849
- name: Publish to PyPI
49-
uses: pypa/gh-action-pypi-publish@e9ccbe5a211ba3e8363f472cae362b56b104e796
50+
uses: pypa/gh-action-pypi-publish@e9ccbe5a211ba3e8363f472cae362b56b104e796

.github/workflows/test.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
---
12
name: Tests
23

34
on:
45
push:
5-
branches: [ main ]
6+
branches: [main]
67
pull_request:
7-
branches: [ main ]
8+
branches: [main]
89

910
jobs:
1011
python:
@@ -52,4 +53,4 @@ jobs:
5253
run: poetry run mypy .
5354

5455
- name: Test
55-
run: poetry run pytest
56+
run: poetry run pytest

.hooks/generate_docs.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import argparse # noqa: INP001
2+
import re
3+
import typing as t
4+
from pathlib import Path
5+
6+
from markdown import Markdown # type: ignore[import-untyped]
7+
from markdownify import MarkdownConverter # type: ignore[import-untyped]
8+
from markupsafe import Markup
9+
from mkdocstrings_handlers.python._internal.config import PythonConfig
10+
from mkdocstrings_handlers.python._internal.handler import (
11+
PythonHandler,
12+
)
13+
14+
# ruff: noqa: T201
15+
16+
17+
class CustomMarkdownConverter(MarkdownConverter): # type: ignore[misc]
18+
# Strip extra whitespace from code blocks
19+
def convert_pre(self, el: t.Any, text: str, parent_tags: t.Any) -> t.Any:
20+
return super().convert_pre(el, text.strip(), parent_tags)
21+
22+
# bold items with doc-section-title in a span class
23+
def convert_span(self, el: t.Any, text: str, parent_tags: t.Any) -> t.Any: # noqa: ARG002
24+
if "doc-section-title" in el.get("class", []):
25+
return f"**{text.strip()}**"
26+
return text
27+
28+
# Remove the div wrapper for inline descriptions
29+
def convert_div(self, el: t.Any, text: str, parent_tags: t.Any) -> t.Any:
30+
if "doc-md-description" in el.get("class", []):
31+
return text.strip()
32+
return super().convert_div(el, text, parent_tags)
33+
34+
# Map mkdocstrings details classes to Mintlify callouts
35+
def convert_details(self, el: t.Any, text: str, parent_tags: t.Any) -> t.Any: # noqa: ARG002
36+
classes = el.get("class", [])
37+
38+
# Handle source code details specially
39+
if "quote" in classes:
40+
summary = el.find("summary")
41+
if summary:
42+
file_path = summary.get_text().replace("Source code in ", "").strip()
43+
content = text[text.find("```") :]
44+
return f'\n<Accordion title="Source code in {file_path}" icon="code">\n{content}\n</Accordion>\n'
45+
46+
callout_map = {
47+
"note": "Note",
48+
"warning": "Warning",
49+
"info": "Info",
50+
"tip": "Tip",
51+
}
52+
53+
callout_type = None
54+
for cls in classes:
55+
if cls in callout_map:
56+
callout_type = callout_map[cls]
57+
break
58+
59+
if not callout_type:
60+
return text
61+
62+
content = text.strip()
63+
if content.startswith(callout_type):
64+
content = content[len(callout_type) :].strip()
65+
66+
return f"\n<{callout_type}>\n{content}\n</{callout_type}>\n"
67+
68+
def convert_table(self, el: t.Any, text: str, parent_tags: t.Any) -> t.Any:
69+
# Check if this is a highlighttable (source code with line numbers)
70+
if "highlighttable" in el.get("class", []):
71+
code_cells = el.find_all("td", class_="code")
72+
if code_cells:
73+
code = code_cells[0].get_text()
74+
code = code.strip()
75+
code = code.replace("```", "~~~")
76+
return f"\n```python\n{code}\n```\n"
77+
78+
return super().convert_table(el, text, parent_tags)
79+
80+
81+
class AutoDocGenerator:
82+
def __init__(self, source_paths: list[str], theme: str = "material", **options: t.Any) -> None:
83+
self.source_paths = source_paths
84+
self.theme = theme
85+
self.handler = PythonHandler(PythonConfig.from_data(), base_dir=Path.cwd())
86+
self.options = options
87+
88+
self.handler._update_env( # noqa: SLF001
89+
Markdown(),
90+
config={"mdx": ["toc"]},
91+
)
92+
93+
md = Markdown(extensions=["fenced_code"])
94+
95+
def simple_convert_markdown(
96+
text: str,
97+
heading_level: int,
98+
html_id: str = "",
99+
**kwargs: t.Any,
100+
) -> t.Any:
101+
return Markup(md.convert(text) if text else "") # noqa: S704 # nosec
102+
103+
self.handler.env.filters["convert_markdown"] = simple_convert_markdown
104+
105+
def generate_docs_for_module(
106+
self,
107+
module_path: str,
108+
) -> str:
109+
options = self.handler.get_options(
110+
{
111+
"docstring_section_style": "list",
112+
"merge_init_into_class": True,
113+
"show_signature_annotations": True,
114+
"separate_signature": True,
115+
"show_source": True,
116+
"show_labels": False,
117+
"show_bases": False,
118+
**self.options,
119+
},
120+
)
121+
122+
module_data = self.handler.collect(module_path, options)
123+
html = self.handler.render(module_data, options)
124+
125+
return str(
126+
CustomMarkdownConverter(
127+
code_language="python",
128+
).convert(html),
129+
)
130+
131+
def process_mdx_file(self, file_path: Path) -> bool:
132+
content = file_path.read_text(encoding="utf-8")
133+
original_content = content
134+
135+
# Find the header comment block
136+
header_match = re.search(
137+
r"\{\s*/\*\s*((?:::.*?\n?)*)\s*\*/\s*\}",
138+
content,
139+
re.MULTILINE | re.DOTALL,
140+
)
141+
142+
if not header_match:
143+
return False
144+
145+
header = header_match.group(0)
146+
module_lines = header_match.group(1).strip().split("\n")
147+
148+
# Generate content for each module
149+
markdown_blocks = []
150+
for line in module_lines:
151+
if line.startswith(":::"):
152+
module_path = line.strip()[3:].strip()
153+
if module_path:
154+
markdown = self.generate_docs_for_module(module_path)
155+
markdown_blocks.append(markdown)
156+
157+
keep_end = content.find(header) + len(header)
158+
new_content = content[:keep_end] + "\n\n" + "\n".join(markdown_blocks)
159+
160+
# Write back if changed
161+
if new_content != original_content:
162+
file_path.write_text(new_content, encoding="utf-8")
163+
print(f"[+] Updated: {file_path}")
164+
return True
165+
166+
return False
167+
168+
def process_directory(self, directory: Path, pattern: str = "**/*.mdx") -> int:
169+
if not directory.exists():
170+
print(f"[!] Directory does not exist: {directory}")
171+
return 0
172+
173+
files_processed = 0
174+
files_modified = 0
175+
176+
for mdx_file in directory.glob(pattern):
177+
if mdx_file.is_file():
178+
files_processed += 1
179+
if self.process_mdx_file(mdx_file):
180+
files_modified += 1
181+
182+
return files_modified
183+
184+
185+
def main() -> None:
186+
"""Main entry point for the script."""
187+
188+
parser = argparse.ArgumentParser(description="Generate auto-docs for MDX files")
189+
parser.add_argument("--directory", help="Directory containing MDX files", default="docs")
190+
parser.add_argument("--pattern", default="**/*.mdx", help="File pattern to match")
191+
parser.add_argument(
192+
"--source-paths",
193+
nargs="+",
194+
default=["dreadnode"],
195+
help="Python source paths for module discovery",
196+
)
197+
parser.add_argument(
198+
"--show-if-no-docstring",
199+
type=bool,
200+
default=False,
201+
help="Show module/class/function even if no docstring is present",
202+
)
203+
parser.add_argument("--theme", default="material", help="Theme to use for rendering")
204+
205+
args = parser.parse_args()
206+
207+
# Create generator
208+
generator = AutoDocGenerator(
209+
source_paths=args.source_paths,
210+
theme=args.theme,
211+
show_if_no_docstring=args.show_if_no_docstring,
212+
)
213+
214+
# Process directory
215+
directory = Path(args.directory)
216+
modified_count = generator.process_directory(directory, args.pattern)
217+
218+
print(f"\n[+] Auto-doc generation complete. {modified_count} files were updated.")
219+
220+
221+
if __name__ == "__main__":
222+
main()

.hooks/typing_and_linting.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
poetry run mypy .
6+
poetry run ruff check .
7+
poetry run ruff format --check .

.pre-commit-config.yaml

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ repos:
1414
- id: check-yaml
1515
- id: detect-private-key
1616
- id: end-of-file-fixer
17+
exclude: ^docs/
1718
- id: trailing-whitespace
1819

1920
- repo: https://github.com/rhysd/actionlint
@@ -31,7 +32,7 @@ repos:
3132
rev: v2.4.1
3233
hooks:
3334
- id: codespell
34-
entry: codespell -q 3 -f --skip=".git,.github,README.md" --ignore-words-list="astroid,braket,te"
35+
entry: codespell -q 3 -f --skip=".git,.github,README.md" -L astroid,braket,te,ROUGE
3536

3637
# Python code security
3738
- repo: https://github.com/PyCQA/bandit
@@ -57,22 +58,6 @@ repos:
5758
- id: nbstripout
5859
args: [--keep-id]
5960

60-
# - repo: https://github.com/astral-sh/ruff-pre-commit
61-
# rev: v0.11.7
62-
# hooks:
63-
# - id: ruff
64-
# args: [--fix]
65-
# - id: ruff-format
66-
67-
# - repo: https://github.com/pre-commit/mirrors-mypy
68-
# rev: v1.15.0
69-
# hooks:
70-
# - id: mypy
71-
# additional_dependencies:
72-
# - "types-PyYAML"
73-
# - "types-requests"
74-
# - "types-setuptools"
75-
7661
- repo: local
7762
hooks:
7863
# Ensure our GH actions are pinned to a specific hash
@@ -82,8 +67,33 @@ repos:
8267
language: python
8368
files: \.github/.*\.yml$
8469

70+
# Format JSON and YAML files
8571
- id: prettier
8672
name: Run prettier
8773
entry: .hooks/prettier.sh
8874
language: script
8975
types: [json, yaml]
76+
77+
# Post-merge hook to refresh dependencies
78+
- id: refresh-dependencies
79+
name: Refresh Dependencies
80+
entry: .hooks/refresh_dependencies.sh
81+
language: script
82+
stages: [post-merge]
83+
always_run: true
84+
85+
# Pre-push hook to run typing and linting
86+
- id: typing-and-linting
87+
name: Typing and Linting
88+
entry: .hooks/typing_and_linting.sh
89+
language: script
90+
stages: [pre-push]
91+
always_run: true
92+
93+
# Generate documentation
94+
- id: generate-docs
95+
name: Generate docs
96+
entry: poetry run python .hooks/generate_docs.py
97+
language: system
98+
pass_filenames: false
99+
always_run: true

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
"ms-python.mypy-type-checker",
88
"tamasfe.even-better-toml"
99
]
10-
}
10+
}

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
],
1313
"python.testing.unittestEnabled": false,
1414
"python.testing.pytestEnabled": true
15-
}
15+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,4 @@ Read through our **[introduction guide](https://docs.dreadnode.io/strikes/intro)
9797

9898
## Examples
9999

100-
Check out **[dreadnode/example-agents](https://github.com/dreadnode/example-agents)** to find your favorite use case.
100+
Check out **[dreadnode/example-agents](https://github.com/dreadnode/example-agents)** to find your favorite use case.

0 commit comments

Comments
 (0)