diff --git a/docs/.gitignore b/docs/.gitignore index 3d5538b288c4..4218543238ad 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,5 @@ _build +references/docs/* references/docstrings/cms references/docstrings/common references/docstrings/lms diff --git a/docs/conf.py b/docs/conf.py index 8f53a154e16a..027e56808b6a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,6 +23,7 @@ sys.path.insert(0, root) sys.path.append(root / "docs") +from repository_docs import RepositoryDocs # Use a settings module that allows all LMS and Studio code to be imported # without errors. If running sphinx-apidoc, we already set a different @@ -307,6 +308,9 @@ def on_init(app): # lint-amnesty, pylint: disable=redefined-outer-name, unused- Read the Docs won't run tox or custom shell commands, so we need this to avoid checking in the generated reStructuredText files. """ + repo_docs_build_path = f'{root}/docs/references/docs' + RepositoryDocs(root, repo_docs_build_path).build_rst_docs() + docs_path = root / 'docs' apidoc_path = 'sphinx-apidoc' if hasattr(sys, 'real_prefix'): # Check to see if we are in a virtualenv diff --git a/docs/references/index.rst b/docs/references/index.rst index bdafc2168837..ec04946987fc 100644 --- a/docs/references/index.rst +++ b/docs/references/index.rst @@ -7,4 +7,5 @@ References :caption: Table of Contents * + docs/index docstrings/index diff --git a/docs/repository_docs.py b/docs/repository_docs.py new file mode 100644 index 000000000000..2b974ffe4b66 --- /dev/null +++ b/docs/repository_docs.py @@ -0,0 +1,96 @@ +import fnmatch +import os +import shutil + +DEFAULT_PATTERNS_TO_EXCLUDE_DIRS = ( + '*.tox', + '*.git', + '*__pycache__', + '*.github', + '*.pytest_cache', + 'build', + 'docs', + 'node_modules', + 'src', + 'test_root', +) + +DEFAULT_PATTERNS_TO_EXCLUDE_FILES = ( + 'readme.rst', + 'changelog.rst', +) + + +class RepositoryDocs: + def __init__( + self, + root, + build_path, + patterns_to_exclude_dirs=DEFAULT_PATTERNS_TO_EXCLUDE_DIRS, + patterns_to_exclude_files=DEFAULT_PATTERNS_TO_EXCLUDE_FILES, + ): + self.root = root + self.build_path = build_path + self.patterns_to_exclude_dirs = patterns_to_exclude_dirs + self.patterns_to_exclude_files = patterns_to_exclude_files + + def build_rst_docs(self): + os.makedirs(self.build_path, exist_ok=True) + self._create_index_rst_file(self.build_path) + rst_files = self._find_rst_files() + self._copy_files(rst_files) + + def _copy_files(self, files): + for file in files: + if file.name.lower() in self.patterns_to_exclude_files: + continue + relative_path = os.path.relpath(os.path.dirname(file), self.root) + destination_path = os.path.join(self.build_path, relative_path) + os.makedirs(destination_path, exist_ok=True) + shutil.copy(file, destination_path) + self._create_index_rst_files_on_path(destination_path) + + def _create_index_rst_files_on_path(self, path): + directory_paths = self._get_directories_list_on_path(path) + for directory_path in directory_paths: + self._create_index_rst_file(directory_path) + + def _get_directories_list_on_path(self, path): + directory_paths = [] + while path and path != self.root: + directory_paths.append(path) + path = os.path.dirname(path) + return directory_paths + + def _create_index_rst_file(self, directory_path): + directory_name = os.path.basename(directory_path) + file_path = f"{directory_path}/index.rst" + if os.path.exists(file_path): + return + file_content = f"""{directory_name} +{len(directory_name) * '='} + +.. toctree:: + :glob: + :maxdepth: 1 + + * + */*index +""" + with open(file_path, "w") as file: + file.write(file_content) + + def _find_rst_files(self): + rst_files = [] + for dir_path, dir_names, file_names in os.walk(self.root): + for excluded_dir in self.patterns_to_exclude_dirs: + if fnmatch.fnmatch(dir_path, f'{self.root}/{excluded_dir}*'): + dir_names.clear() + file_names.clear() + break + for file_name in file_names: + if file_name.lower().endswith('.rst'): + rst_files.append(os.path.join(dir_path, file_name)) + if '__pycache__' in dir_names: + dir_names.remove('__pycache__') + return rst_files