Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

build_reference.py 6.0 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
  1. # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
  2. """
  3. Helper file to build Ultralytics Docs reference section.
  4. This script recursively walks through the ultralytics directory and builds an MkDocs reference section of *.md files
  5. composed of classes and functions, and also creates a navigation menu for use in mkdocs.yaml.
  6. Note: Must be run from repository root directory. Do not run from docs directory.
  7. """
  8. import re
  9. import subprocess
  10. from collections import defaultdict
  11. from pathlib import Path
  12. # Constants
  13. hub_sdk = False
  14. if hub_sdk:
  15. PACKAGE_DIR = Path("/Users/glennjocher/PycharmProjects/hub-sdk/hub_sdk")
  16. REFERENCE_DIR = PACKAGE_DIR.parent / "docs/reference"
  17. GITHUB_REPO = "ultralytics/hub-sdk"
  18. else:
  19. FILE = Path(__file__).resolve()
  20. PACKAGE_DIR = FILE.parents[1] / "ultralytics" # i.e. /Users/glennjocher/PycharmProjects/ultralytics/ultralytics
  21. REFERENCE_DIR = PACKAGE_DIR.parent / "docs/en/reference"
  22. GITHUB_REPO = "ultralytics/ultralytics"
  23. def extract_classes_and_functions(filepath: Path) -> tuple:
  24. """Extracts class and function names from a given Python file."""
  25. content = filepath.read_text()
  26. class_pattern = r"(?:^|\n)class\s(\w+)(?:\(|:)"
  27. func_pattern = r"(?:^|\n)def\s(\w+)\("
  28. classes = re.findall(class_pattern, content)
  29. functions = re.findall(func_pattern, content)
  30. return classes, functions
  31. def create_markdown(py_filepath: Path, module_path: str, classes: list, functions: list) -> Path:
  32. """Creates a Markdown file containing the API reference for the given Python module."""
  33. md_filepath = py_filepath.with_suffix(".md")
  34. exists = md_filepath.exists()
  35. # Read existing content and retain header metadata if available
  36. header_content = ""
  37. if exists:
  38. existing_content = md_filepath.read_text()
  39. header_parts = existing_content.split("---")
  40. for part in header_parts:
  41. if "description:" in part or "comments:" in part:
  42. header_content += f"---{part}---\n\n"
  43. if not any(header_content):
  44. header_content = "---\ndescription: TODO ADD DESCRIPTION\nkeywords: TODO ADD KEYWORDS\n---\n\n"
  45. module_name = module_path.replace(".__init__", "")
  46. module_path = module_path.replace(".", "/")
  47. url = f"https://github.com/{GITHUB_REPO}/blob/main/{module_path}.py"
  48. edit = f"https://github.com/{GITHUB_REPO}/edit/main/{module_path}.py"
  49. pretty = url.replace("__init__.py", "\\_\\_init\\_\\_.py") # Properly display __init__.py filenames
  50. title_content = (
  51. f"# Reference for `{module_path}.py`\n\n"
  52. f"!!! note\n\n"
  53. f" This file is available at [{pretty}]({url}). If you spot a problem please help fix it by [contributing]"
  54. f"(https://docs.ultralytics.com/help/contributing/) a [Pull Request]({edit}) 🛠️. Thank you 🙏!\n\n"
  55. )
  56. md_content = ["<br>\n"] + [f"## ::: {module_name}.{class_name}\n\n<br><br><hr><br>\n" for class_name in classes]
  57. md_content.extend(f"## ::: {module_name}.{func_name}\n\n<br><br><hr><br>\n" for func_name in functions)
  58. md_content[-1] = md_content[-1].replace("<hr><br>", "") # Remove last horizontal rule from final entry
  59. md_content = header_content + title_content + "\n".join(md_content)
  60. if not md_content.endswith("\n"):
  61. md_content += "\n"
  62. md_filepath.parent.mkdir(parents=True, exist_ok=True)
  63. md_filepath.write_text(md_content)
  64. if not exists:
  65. # Add new Markdown file to the Git staging area
  66. print(f"Created new file '{md_filepath}'")
  67. subprocess.run(["git", "add", "-f", str(md_filepath)], check=True, cwd=PACKAGE_DIR)
  68. return md_filepath.relative_to(PACKAGE_DIR.parent)
  69. def nested_dict() -> defaultdict:
  70. """Creates and returns a nested defaultdict."""
  71. return defaultdict(nested_dict)
  72. def sort_nested_dict(d: dict) -> dict:
  73. """Sorts a nested dictionary recursively."""
  74. return {key: sort_nested_dict(value) if isinstance(value, dict) else value for key, value in sorted(d.items())}
  75. def create_nav_menu_yaml(nav_items: list, save: bool = False) -> None:
  76. """Creates a YAML file for the navigation menu based on the provided list of items."""
  77. nav_tree = nested_dict()
  78. for item_str in nav_items:
  79. item = Path(item_str)
  80. parts = item.parts
  81. current_level = nav_tree["reference"]
  82. for part in parts[2:-1]: # Skip the first two parts (docs and reference) and the filename
  83. current_level = current_level[part]
  84. md_file_name = parts[-1].replace(".md", "")
  85. current_level[md_file_name] = item
  86. nav_tree_sorted = sort_nested_dict(nav_tree)
  87. def _dict_to_yaml(d, level=0):
  88. """Converts a nested dictionary to a YAML-formatted string with indentation."""
  89. yaml_str = ""
  90. indent = " " * level
  91. for k, v in d.items():
  92. if isinstance(v, dict):
  93. yaml_str += f"{indent}- {k}:\n{_dict_to_yaml(v, level + 1)}"
  94. else:
  95. yaml_str += f"{indent}- {k}: {str(v).replace('docs/en/', '')}\n"
  96. return yaml_str
  97. # Print updated YAML reference section
  98. print("Scan complete, new mkdocs.yaml reference section is:\n\n", _dict_to_yaml(nav_tree_sorted))
  99. # Save new YAML reference section to file if 'save' is True
  100. if save:
  101. (PACKAGE_DIR.parent / "nav_menu_updated.yml").write_text(_dict_to_yaml(nav_tree_sorted))
  102. def main():
  103. """Extract class and function names, create Markdown files, and generate a YAML navigation menu."""
  104. nav_items = []
  105. for py_filepath in PACKAGE_DIR.rglob("*.py"):
  106. classes, functions = extract_classes_and_functions(py_filepath)
  107. if classes or functions:
  108. py_filepath_rel = py_filepath.relative_to(PACKAGE_DIR)
  109. md_filepath = REFERENCE_DIR / py_filepath_rel
  110. module_path = f"{PACKAGE_DIR.name}.{py_filepath_rel.with_suffix('').as_posix().replace('/', '.')}"
  111. md_rel_filepath = create_markdown(md_filepath, module_path, classes, functions)
  112. nav_items.append(str(md_rel_filepath))
  113. create_nav_menu_yaml(nav_items)
  114. if __name__ == "__main__":
  115. main()
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...