ultralytics 8.0.239 Ultralytics Actions and hub-sdk adoption (#7431)

Signed-off-by: Glenn Jocher <glenn.jocher@ultralytics.com>
Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
Co-authored-by: Burhan <62214284+Burhan-Q@users.noreply.github.com>
Co-authored-by: Kayzwer <68285002+Kayzwer@users.noreply.github.com>
This commit is contained in:
Glenn Jocher 2024-01-10 03:16:08 +01:00 committed by GitHub
parent e795277391
commit fe27db2f6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
139 changed files with 6870 additions and 5125 deletions

View file

@ -30,45 +30,47 @@ import subprocess
from pathlib import Path
DOCS = Path(__file__).parent.resolve()
SITE = DOCS.parent / 'site'
SITE = DOCS.parent / "site"
def build_docs():
"""Build docs using mkdocs."""
if SITE.exists():
print(f'Removing existing {SITE}')
print(f"Removing existing {SITE}")
shutil.rmtree(SITE)
# Build the main documentation
print(f'Building docs from {DOCS}')
subprocess.run(f'mkdocs build -f {DOCS}/mkdocs.yml', check=True, shell=True)
print(f"Building docs from {DOCS}")
subprocess.run(f"mkdocs build -f {DOCS}/mkdocs.yml", check=True, shell=True)
# Build other localized documentations
for file in DOCS.glob('mkdocs_*.yml'):
print(f'Building MkDocs site with configuration file: {file}')
subprocess.run(f'mkdocs build -f {file}', check=True, shell=True)
print(f'Site built at {SITE}')
for file in DOCS.glob("mkdocs_*.yml"):
print(f"Building MkDocs site with configuration file: {file}")
subprocess.run(f"mkdocs build -f {file}", check=True, shell=True)
print(f"Site built at {SITE}")
def update_html_links():
"""Update href links in HTML files to remove '.md' and '/index.md', excluding links starting with 'https://'."""
html_files = Path(SITE).rglob('*.html')
html_files = Path(SITE).rglob("*.html")
total_updated_links = 0
for html_file in html_files:
with open(html_file, 'r+', encoding='utf-8') as file:
with open(html_file, "r+", encoding="utf-8") as file:
content = file.read()
# Find all links to be updated, excluding those starting with 'https://'
links_to_update = re.findall(r'href="(?!https://)([^"]+?)(/index)?\.md"', content)
# Update the content and count the number of links updated
updated_content, number_of_links_updated = re.subn(r'href="(?!https://)([^"]+?)(/index)?\.md"',
r'href="\1"', content)
updated_content, number_of_links_updated = re.subn(
r'href="(?!https://)([^"]+?)(/index)?\.md"', r'href="\1"', content
)
total_updated_links += number_of_links_updated
# Special handling for '/index' links
updated_content, number_of_index_links_updated = re.subn(r'href="([^"]+)/index"', r'href="\1/"',
updated_content)
updated_content, number_of_index_links_updated = re.subn(
r'href="([^"]+)/index"', r'href="\1/"', updated_content
)
total_updated_links += number_of_index_links_updated
# Write the updated content back to the file
@ -78,23 +80,23 @@ def update_html_links():
# Print updated links for this file
for link in links_to_update:
print(f'Updated link in {html_file}: {link[0]}')
print(f"Updated link in {html_file}: {link[0]}")
print(f'Total number of links updated: {total_updated_links}')
print(f"Total number of links updated: {total_updated_links}")
def update_page_title(file_path: Path, new_title: str):
"""Update the title of an HTML file."""
# Read the content of the file
with open(file_path, encoding='utf-8') as file:
with open(file_path, encoding="utf-8") as file:
content = file.read()
# Replace the existing title with the new title
updated_content = re.sub(r'<title>.*?</title>', f'<title>{new_title}</title>', content)
updated_content = re.sub(r"<title>.*?</title>", f"<title>{new_title}</title>", content)
# Write the updated content back to the file
with open(file_path, 'w', encoding='utf-8') as file:
with open(file_path, "w", encoding="utf-8") as file:
file.write(updated_content)
@ -109,8 +111,8 @@ def main():
print('Serve site at http://localhost:8000 with "python -m http.server --directory site"')
# Update titles
update_page_title(SITE / '404.html', new_title='Ultralytics Docs - Not Found')
update_page_title(SITE / "404.html", new_title="Ultralytics Docs - Not Found")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -14,14 +14,14 @@ from ultralytics.utils import ROOT
NEW_YAML_DIR = ROOT.parent
CODE_DIR = ROOT
REFERENCE_DIR = ROOT.parent / 'docs/en/reference'
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+)\('
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)
@ -31,31 +31,31 @@ def extract_classes_and_functions(filepath: Path) -> tuple:
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')
md_filepath = py_filepath.with_suffix(".md")
# Read existing content and keep header content between first two ---
header_content = ''
header_content = ""
if md_filepath.exists():
existing_content = md_filepath.read_text()
header_parts = existing_content.split('---')
header_parts = existing_content.split("---")
for part in header_parts:
if 'description:' in part or 'comments:' in part:
header_content += f'---{part}---\n\n'
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'
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'
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 = ['<br><br>\n'] + [f'## ::: {module_name}.{class_name}\n\n<br><br>\n' for class_name in classes]
md_content.extend(f'## ::: {module_name}.{func_name}\n\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_content = ["<br><br>\n"] + [f"## ::: {module_name}.{class_name}\n\n<br><br>\n" for class_name in classes]
md_content.extend(f"## ::: {module_name}.{func_name}\n\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)
@ -80,28 +80,28 @@ def create_nav_menu_yaml(nav_items: list):
for item_str in nav_items:
item = Path(item_str)
parts = item.parts
current_level = nav_tree['reference']
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', '')
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
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)}'
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))
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))
@ -111,7 +111,7 @@ 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'):
for py_filepath in CODE_DIR.rglob("*.py"):
classes, functions = extract_classes_and_functions(py_filepath)
if classes or functions:
@ -124,5 +124,5 @@ def main():
create_nav_menu_yaml(nav_items)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -22,69 +22,232 @@ class MarkdownLinkFixer:
self.base_dir = Path(base_dir)
self.update_links = update_links
self.update_text = update_text
self.md_link_regex = re.compile(r'\[([^]]+)]\(([^:)]+)\.md\)')
self.md_link_regex = re.compile(r"\[([^]]+)]\(([^:)]+)\.md\)")
@staticmethod
def replace_front_matter(content, lang_dir):
"""Ensure front matter keywords remain in English."""
english = ['comments', 'description', 'keywords']
english = ["comments", "description", "keywords"]
translations = {
'zh': ['评论', '描述', '关键词'], # Mandarin Chinese (Simplified) warning, sometimes translates as 关键字
'es': ['comentarios', 'descripción', 'palabras clave'], # Spanish
'ru': ['комментарии', 'описание', 'ключевые слова'], # Russian
'pt': ['comentários', 'descrição', 'palavras-chave'], # Portuguese
'fr': ['commentaires', 'description', 'mots-clés'], # French
'de': ['kommentare', 'beschreibung', 'schlüsselwörter'], # German
'ja': ['コメント', '説明', 'キーワード'], # Japanese
'ko': ['댓글', '설명', '키워드'], # Korean
'hi': ['टिप्पणियाँ', 'विवरण', 'कीवर्ड'], # Hindi
'ar': ['التعليقات', 'الوصف', 'الكلمات الرئيسية'] # Arabic
"zh": ["评论", "描述", "关键词"], # Mandarin Chinese (Simplified) warning, sometimes translates as 关键字
"es": ["comentarios", "descripción", "palabras clave"], # Spanish
"ru": ["комментарии", "описание", "ключевые слова"], # Russian
"pt": ["comentários", "descrição", "palavras-chave"], # Portuguese
"fr": ["commentaires", "description", "mots-clés"], # French
"de": ["kommentare", "beschreibung", "schlüsselwörter"], # German
"ja": ["コメント", "説明", "キーワード"], # Japanese
"ko": ["댓글", "설명", "키워드"], # Korean
"hi": ["टिप्पणियाँ", "विवरण", "कीवर्ड"], # Hindi
"ar": ["التعليقات", "الوصف", "الكلمات الرئيسية"], # Arabic
} # front matter translations for comments, description, keyword
for term, eng_key in zip(translations.get(lang_dir.stem, []), english):
content = re.sub(rf'{term} *[:].*', f'{eng_key}: true', content, flags=re.IGNORECASE) if \
eng_key == 'comments' else re.sub(rf'{term} *[:] *', f'{eng_key}: ', content, flags=re.IGNORECASE)
content = (
re.sub(rf"{term} *[:].*", f"{eng_key}: true", content, flags=re.IGNORECASE)
if eng_key == "comments"
else re.sub(rf"{term} *[:] *", f"{eng_key}: ", content, flags=re.IGNORECASE)
)
return content
@staticmethod
def replace_admonitions(content, lang_dir):
"""Ensure front matter keywords remain in English."""
english = [
'Note', 'Summary', 'Tip', 'Info', 'Success', 'Question', 'Warning', 'Failure', 'Danger', 'Bug', 'Example',
'Quote', 'Abstract', 'Seealso', 'Admonition']
"Note",
"Summary",
"Tip",
"Info",
"Success",
"Question",
"Warning",
"Failure",
"Danger",
"Bug",
"Example",
"Quote",
"Abstract",
"Seealso",
"Admonition",
]
translations = {
'en':
english,
'zh': ['笔记', '摘要', '提示', '信息', '成功', '问题', '警告', '失败', '危险', '故障', '示例', '引用', '摘要', '另见', '警告'],
'es': [
'Nota', 'Resumen', 'Consejo', 'Información', 'Éxito', 'Pregunta', 'Advertencia', 'Fracaso', 'Peligro',
'Error', 'Ejemplo', 'Cita', 'Abstracto', 'Véase También', 'Amonestación'],
'ru': [
'Заметка', 'Сводка', 'Совет', 'Информация', 'Успех', 'Вопрос', 'Предупреждение', 'Неудача', 'Опасность',
'Ошибка', 'Пример', 'Цитата', 'Абстракт', 'См. Также', 'Предостережение'],
'pt': [
'Nota', 'Resumo', 'Dica', 'Informação', 'Sucesso', 'Questão', 'Aviso', 'Falha', 'Perigo', 'Bug',
'Exemplo', 'Citação', 'Abstrato', 'Veja Também', 'Advertência'],
'fr': [
'Note', 'Résumé', 'Conseil', 'Info', 'Succès', 'Question', 'Avertissement', 'Échec', 'Danger', 'Bug',
'Exemple', 'Citation', 'Abstrait', 'Voir Aussi', 'Admonestation'],
'de': [
'Hinweis', 'Zusammenfassung', 'Tipp', 'Info', 'Erfolg', 'Frage', 'Warnung', 'Ausfall', 'Gefahr',
'Fehler', 'Beispiel', 'Zitat', 'Abstrakt', 'Siehe Auch', 'Ermahnung'],
'ja': ['ノート', '要約', 'ヒント', '情報', '成功', '質問', '警告', '失敗', '危険', 'バグ', '', '引用', '抄録', '参照', '訓告'],
'ko': ['노트', '요약', '', '정보', '성공', '질문', '경고', '실패', '위험', '버그', '예제', '인용', '추상', '참조', '경고'],
'hi': [
'नोट', 'सारांश', 'सुझाव', 'जानकारी', 'सफलता', 'प्रश्न', 'चेतावनी', 'विफलता', 'खतरा', 'बग', 'उदाहरण',
'उद्धरण', 'सार', 'देखें भी', 'आगाही'],
'ar': [
'ملاحظة', 'ملخص', 'نصيحة', 'معلومات', 'نجاح', 'سؤال', 'تحذير', 'فشل', 'خطر', 'عطل', 'مثال', 'اقتباس',
'ملخص', 'انظر أيضاً', 'تحذير']}
"en": english,
"zh": [
"笔记",
"摘要",
"提示",
"信息",
"成功",
"问题",
"警告",
"失败",
"危险",
"故障",
"示例",
"引用",
"摘要",
"另见",
"警告",
],
"es": [
"Nota",
"Resumen",
"Consejo",
"Información",
"Éxito",
"Pregunta",
"Advertencia",
"Fracaso",
"Peligro",
"Error",
"Ejemplo",
"Cita",
"Abstracto",
"Véase También",
"Amonestación",
],
"ru": [
"Заметка",
"Сводка",
"Совет",
"Информация",
"Успех",
"Вопрос",
"Предупреждение",
"Неудача",
"Опасность",
"Ошибка",
"Пример",
"Цитата",
"Абстракт",
"См. Также",
"Предостережение",
],
"pt": [
"Nota",
"Resumo",
"Dica",
"Informação",
"Sucesso",
"Questão",
"Aviso",
"Falha",
"Perigo",
"Bug",
"Exemplo",
"Citação",
"Abstrato",
"Veja Também",
"Advertência",
],
"fr": [
"Note",
"Résumé",
"Conseil",
"Info",
"Succès",
"Question",
"Avertissement",
"Échec",
"Danger",
"Bug",
"Exemple",
"Citation",
"Abstrait",
"Voir Aussi",
"Admonestation",
],
"de": [
"Hinweis",
"Zusammenfassung",
"Tipp",
"Info",
"Erfolg",
"Frage",
"Warnung",
"Ausfall",
"Gefahr",
"Fehler",
"Beispiel",
"Zitat",
"Abstrakt",
"Siehe Auch",
"Ermahnung",
],
"ja": [
"ノート",
"要約",
"ヒント",
"情報",
"成功",
"質問",
"警告",
"失敗",
"危険",
"バグ",
"",
"引用",
"抄録",
"参照",
"訓告",
],
"ko": [
"노트",
"요약",
"",
"정보",
"성공",
"질문",
"경고",
"실패",
"위험",
"버그",
"예제",
"인용",
"추상",
"참조",
"경고",
],
"hi": [
"नोट",
"सारांश",
"सुझाव",
"जानकारी",
"सफलता",
"प्रश्न",
"चेतावनी",
"विफलता",
"खतरा",
"बग",
"उदाहरण",
"उद्धरण",
"सार",
"देखें भी",
"आगाही",
],
"ar": [
"ملاحظة",
"ملخص",
"نصيحة",
"معلومات",
"نجاح",
"سؤال",
"تحذير",
"فشل",
"خطر",
"عطل",
"مثال",
"اقتباس",
"ملخص",
"انظر أيضاً",
"تحذير",
],
}
for term, eng_key in zip(translations.get(lang_dir.stem, []), english):
if lang_dir.stem != 'en':
content = re.sub(rf'!!! *{eng_key} *\n', f'!!! {eng_key} "{term}"\n', content, flags=re.IGNORECASE)
content = re.sub(rf'!!! *{term} *\n', f'!!! {eng_key} "{term}"\n', content, flags=re.IGNORECASE)
content = re.sub(rf'!!! *{term}', f'!!! {eng_key}', content, flags=re.IGNORECASE)
if lang_dir.stem != "en":
content = re.sub(rf"!!! *{eng_key} *\n", f'!!! {eng_key} "{term}"\n', content, flags=re.IGNORECASE)
content = re.sub(rf"!!! *{term} *\n", f'!!! {eng_key} "{term}"\n', content, flags=re.IGNORECASE)
content = re.sub(rf"!!! *{term}", f"!!! {eng_key}", content, flags=re.IGNORECASE)
content = re.sub(r'!!! *"', '!!! Example "', content, flags=re.IGNORECASE)
return content
@ -92,30 +255,30 @@ class MarkdownLinkFixer:
@staticmethod
def update_iframe(content):
"""Update the 'allow' attribute of iframe if it does not contain the specific English permissions."""
english = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
english = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
pattern = re.compile(f'allow="(?!{re.escape(english)}).+?"')
return pattern.sub(f'allow="{english}"', content)
def link_replacer(self, match, parent_dir, lang_dir, use_abs_link=False):
"""Replace broken links with corresponding links in the /en/ directory."""
text, path = match.groups()
linked_path = (parent_dir / path).resolve().with_suffix('.md')
linked_path = (parent_dir / path).resolve().with_suffix(".md")
if not linked_path.exists():
en_linked_path = Path(str(linked_path).replace(str(lang_dir), str(lang_dir.parent / 'en')))
en_linked_path = Path(str(linked_path).replace(str(lang_dir), str(lang_dir.parent / "en")))
if en_linked_path.exists():
if use_abs_link:
# Use absolute links WARNING: BUGS, DO NOT USE
docs_root_relative_path = en_linked_path.relative_to(lang_dir.parent)
updated_path = str(docs_root_relative_path).replace('en/', '/../')
updated_path = str(docs_root_relative_path).replace("en/", "/../")
else:
# Use relative links
steps_up = len(parent_dir.relative_to(self.base_dir).parts)
updated_path = Path('../' * steps_up) / en_linked_path.relative_to(self.base_dir)
updated_path = str(updated_path).replace('/en/', '/')
updated_path = Path("../" * steps_up) / en_linked_path.relative_to(self.base_dir)
updated_path = str(updated_path).replace("/en/", "/")
print(f"Redirecting link '[{text}]({path})' from {parent_dir} to {updated_path}")
return f'[{text}]({updated_path})'
return f"[{text}]({updated_path})"
else:
print(f"Warning: Broken link '[{text}]({path})' found in {parent_dir} does not exist in /docs/en/.")
@ -124,28 +287,30 @@ class MarkdownLinkFixer:
@staticmethod
def update_html_tags(content):
"""Updates HTML tags in docs."""
alt_tag = 'MISSING'
alt_tag = "MISSING"
# Remove closing slashes from self-closing HTML tags
pattern = re.compile(r'<([^>]+?)\s*/>')
content = re.sub(pattern, r'<\1>', content)
pattern = re.compile(r"<([^>]+?)\s*/>")
content = re.sub(pattern, r"<\1>", content)
# Find all images without alt tags and add placeholder alt text
pattern = re.compile(r'!\[(.*?)\]\((.*?)\)')
content, num_replacements = re.subn(pattern, lambda match: f'![{match.group(1) or alt_tag}]({match.group(2)})',
content)
pattern = re.compile(r"!\[(.*?)\]\((.*?)\)")
content, num_replacements = re.subn(
pattern, lambda match: f"![{match.group(1) or alt_tag}]({match.group(2)})", content
)
# Add missing alt tags to HTML images
pattern = re.compile(r'<img\s+(?!.*?\balt\b)[^>]*src=["\'](.*?)["\'][^>]*>')
content, num_replacements = re.subn(pattern, lambda match: match.group(0).replace('>', f' alt="{alt_tag}">', 1),
content)
content, num_replacements = re.subn(
pattern, lambda match: match.group(0).replace(">", f' alt="{alt_tag}">', 1), content
)
return content
def process_markdown_file(self, md_file_path, lang_dir):
"""Process each markdown file in the language directory."""
print(f'Processing file: {md_file_path}')
with open(md_file_path, encoding='utf-8') as file:
print(f"Processing file: {md_file_path}")
with open(md_file_path, encoding="utf-8") as file:
content = file.read()
if self.update_links:
@ -157,23 +322,23 @@ class MarkdownLinkFixer:
content = self.update_iframe(content)
content = self.update_html_tags(content)
with open(md_file_path, 'w', encoding='utf-8') as file:
with open(md_file_path, "w", encoding="utf-8") as file:
file.write(content)
def process_language_directory(self, lang_dir):
"""Process each language-specific directory."""
print(f'Processing language directory: {lang_dir}')
for md_file in lang_dir.rglob('*.md'):
print(f"Processing language directory: {lang_dir}")
for md_file in lang_dir.rglob("*.md"):
self.process_markdown_file(md_file, lang_dir)
def run(self):
"""Run the link fixing and front matter updating process for each language-specific directory."""
for subdir in self.base_dir.iterdir():
if subdir.is_dir() and re.match(r'^\w\w$', subdir.name):
if subdir.is_dir() and re.match(r"^\w\w$", subdir.name):
self.process_language_directory(subdir)
if __name__ == '__main__':
if __name__ == "__main__":
# Set the path to your MkDocs 'docs' directory here
docs_dir = str(Path(__file__).parent.resolve())
fixer = MarkdownLinkFixer(docs_dir, update_links=True, update_text=True)