# Ultralytics YOLO 🚀, AGPL-3.0 license
"""
Helper file to build Ultralytics Docs reference section . Recursively walks through ultralytics dir and builds an MkDocs
reference section of * . md files composed of classes and functions , and also creates a nav menu for use in mkdocs . yaml .
Note : Must be run from repository root directory . Do not run from docs directory .
"""
import re
from collections import defaultdict
from pathlib import Path
from ultralytics . utils import ROOT
NEW_YAML_DIR = ROOT . parent
CODE_DIR = ROOT
REFERENCE_DIR = ROOT . parent / ' docs/en/reference '
def extract_classes_and_functions ( filepath : Path ) - > tuple :
""" Extracts class and function names from a given Python file. """
content = filepath . read_text ( )
class_pattern = r ' (?:^| \ n)class \ s( \ w+)(?: \ (|:) '
func_pattern = r ' (?:^| \ n)def \ s( \ w+) \ ( '
classes = re . findall ( class_pattern , content )
functions = re . findall ( func_pattern , content )
return classes , functions
def create_markdown ( py_filepath : Path , module_path : str , classes : list , functions : list ) :
""" Creates a Markdown file containing the API reference for the given Python module. """
md_filepath = py_filepath . with_suffix ( ' .md ' )
# Read existing content and keep header content between first two ---
header_content = ' '
if md_filepath . exists ( ) :
existing_content = md_filepath . read_text ( )
header_parts = existing_content . split ( ' --- ' )
for part in header_parts :
if ' description: ' in part or ' comments: ' in part :
header_content + = f ' --- { part } --- \n \n '
module_name = module_path . replace ( ' .__init__ ' , ' ' )
module_path = module_path . replace ( ' . ' , ' / ' )
url = f ' https://github.com/ultralytics/ultralytics/blob/main/ { module_path } .py '
edit = f ' https://github.com/ultralytics/ultralytics/edit/main/ { module_path } .py '
title_content = (
f ' # Reference for ` { module_path } .py` \n \n '
f ' !!! note \n \n '
f ' This file is available at [ { url } ]( { url } ). If you spot a problem please help fix it by [contributing](https://docs.ultralytics.com/help/contributing/) a [Pull Request]( { edit } ) 🛠️ . Thank you 🙏! \n \n '
)
md_content = [ f ' --- \n ## ::: { module_name } . { class_name } \n <br><br> \n ' for class_name in classes ]
md_content . extend ( f ' --- \n ## ::: { module_name } . { func_name } \n <br><br> \n ' for func_name in functions )
md_content = header_content + title_content + ' \n ' . join ( md_content )
if not md_content . endswith ( ' \n ' ) :
md_content + = ' \n '
md_filepath . parent . mkdir ( parents = True , exist_ok = True )
md_filepath . write_text ( md_content )
return md_filepath . relative_to ( NEW_YAML_DIR )
def nested_dict ( ) - > defaultdict :
""" Creates and returns a nested defaultdict. """
return defaultdict ( nested_dict )
def sort_nested_dict ( d : dict ) - > dict :
""" Sorts a nested dictionary recursively. """
return { key : sort_nested_dict ( value ) if isinstance ( value , dict ) else value for key , value in sorted ( d . items ( ) ) }
def create_nav_menu_yaml ( nav_items : list ) :
""" Creates a YAML file for the navigation menu based on the provided list of items. """
nav_tree = nested_dict ( )
for item_str in nav_items :
item = Path ( item_str )
parts = item . parts
current_level = nav_tree [ ' reference ' ]
for part in parts [ 2 : - 1 ] : # skip the first two parts (docs and reference) and the last part (filename)
current_level = current_level [ part ]
md_file_name = parts [ - 1 ] . replace ( ' .md ' , ' ' )
current_level [ md_file_name ] = item
nav_tree_sorted = sort_nested_dict ( nav_tree )
def _dict_to_yaml ( d , level = 0 ) :
""" Converts a nested dictionary to a YAML-formatted string with indentation. """
yaml_str = ' '
indent = ' ' * level
for k , v in d . items ( ) :
if isinstance ( v , dict ) :
yaml_str + = f ' { indent } - { k } : \n { _dict_to_yaml ( v , level + 1 ) } '
else :
yaml_str + = f " { indent } - { k } : { str ( v ) . replace ( ' docs/en/ ' , ' ' ) } \n "
return yaml_str
# Print updated YAML reference section
print ( ' Scan complete, new mkdocs.yaml reference section is: \n \n ' , _dict_to_yaml ( nav_tree_sorted ) )
# Save new YAML reference section
# (NEW_YAML_DIR / 'nav_menu_updated.yml').write_text(_dict_to_yaml(nav_tree_sorted))
def main ( ) :
""" Main function to extract class and function names, create Markdown files, and generate a YAML navigation menu. """
nav_items = [ ]
for py_filepath in CODE_DIR . rglob ( ' *.py ' ) :
classes , functions = extract_classes_and_functions ( py_filepath )
if classes or functions :
py_filepath_rel = py_filepath . relative_to ( CODE_DIR )
md_filepath = REFERENCE_DIR / py_filepath_rel
module_path = f " ultralytics. { py_filepath_rel . with_suffix ( ' ' ) . as_posix ( ) . replace ( ' / ' , ' . ' ) } "
md_rel_filepath = create_markdown ( md_filepath , module_path , classes , functions )
nav_items . append ( str ( md_rel_filepath ) )
create_nav_menu_yaml ( nav_items )
if __name__ == ' __main__ ' :
main ( )