"""Module with functions for 'doc' subpackage."""
from __future__ import annotations
from typing import Sequence
import ast
from typing_extensions import Literal
from mypythontools.paths import validate_path, PathLike
from mypythontools.types import validate_sequence
from mypythontools.misc import delete_files, print_progress
from mypythontools.system import (
check_script_is_available,
get_console_str_with_quotes,
terminal_do_command,
SHELL_AND,
)
from mypythontools_cicd.project_paths import PROJECT_PATHS
[docs]def docs_regenerate(
docs_path: None | PathLike = None,
build_locally: bool = True,
git_add: bool = True,
keep: Sequence[PathLike] = (),
ignore: Sequence[PathLike] = ("modules.rst", "**/*internal.py"),
verbosity: Literal[0, 1, 2] = 1,
) -> None:
"""Generate all rst files necessary for sphinx documentation generation with sphinx-apidoc.
It automatically delete rst files from removed or renamed files.
Note:
All the files except ['conf.py', 'index.rst', '_static', '_templates', 'content/**'], and files in
'keep' parameter will be deleted!!! Because if some files would be deleted or
renamed, rst would stay and html was generated. If you have some extra files or folders in docs
source, add it to content folder or to 'keep' parameter.
Function suppose sphinx build and source in separate folders...
Args:
docs_path (None | PathLike, optional): Where source folder is. If None, will be inferred.
Defaults to None.
build_locally (bool, optional): If true, build folder with html files locally.
Defaults to True.
git_add (bool, optional): Whether to add generated files to stage. False mostly for
testing reasons. Defaults to True.
keep (Sequence[PathLike], optional): List of files and folder names that will not be
deleted. Deletion is because if some file would be renamed or deleted, rst docs would still stay.
Glob-style patterns can be used, but it's not recursive, but only first level of source folder is
used. Defaults to None.
ignore (Sequence[PathLike], optional): Whether ignore some files from generated rst files. For example
It can be python modules that will be ignored or it can be rst files created, that will be
deleted. to have no errors in sphinx build for unused modules, or for internal modules. Glob-style
patterns can be used. Defaults to ("modules.rst", "**/*_.py")
verbosity (Literal[0, 1, 2], optional): If 0, prints nothing, if 1, then one line description of what
happened is printed. If 3, all the results from terminal are printed. Defaults to 1.
Note:
Function suppose structure of docs like::
-- docs
-- -- source
-- -- -- conf.py
-- -- make.bat
"""
print_progress("Sphinx docs generation", verbosity > 0)
check_script_is_available("sphinx-apidoc", "sphinx")
validate_sequence(keep, keep)
validate_sequence(ignore, ignore)
verbose = verbosity == 2
docs_path = (
validate_path(docs_path, "Docs generation with 'docs_regenerate' failed", "Docs path")
if docs_path
else PROJECT_PATHS.docs
)
docs_source_path = docs_path / "source"
source_path = PROJECT_PATHS.app
source_console_path = get_console_str_with_quotes(source_path)
keep = [
*keep,
"conf.py",
"index.rst",
"_static",
"_templates",
"content",
]
ignore_list = [*ignore]
ignored = " "
for i in ignore_list:
for file in source_path.rglob(str(i)):
ignored = ignored + f"{get_console_str_with_quotes(file)} "
for file in docs_source_path.iterdir():
if not any((file.match(str(pattern)) for pattern in keep)):
delete_files(file)
apidoc_command = (
f"sphinx-apidoc --module-first --force --separate -o source {source_console_path} {ignored}"
)
terminal_do_command(
apidoc_command, cwd=docs_path, verbose=verbose, error_header="Docs sphinx-apidoc failed."
)
if ignore_list:
for file in docs_source_path.iterdir():
if any((file.match(str(pattern)) for pattern in ignore_list)):
delete_files(file)
if build_locally:
terminal_do_command(
f"make clean {SHELL_AND} make html",
cwd=docs_path,
verbose=verbose,
error_header="Sphinx build failed.",
shell=True,
)
if git_add:
terminal_do_command("git add docs", cwd=PROJECT_PATHS.root.as_posix(), verbose=verbose)
[docs]def generate_readme_from_init(git_add: bool = True) -> None:
"""Generate README file from `__init__.py` docstrings.
Because i had very similar things in main `__init__.py` and in readme. It was to maintain news
in code. For better simplicity i prefer write docs once and then generate. One code, two use cases.
Why `__init__`? - Because in IDE on mouseover developers can see help.
Why README.md? - Good for github.com
Args:
git_add (bool, optional): Whether to add generated files to stage. False mostly
for testing reasons. Defaults to True.
"""
with open(PROJECT_PATHS.init.as_posix()) as init_file:
file_contents = init_file.read()
module = ast.parse(file_contents)
docstrings = ast.get_docstring(module)
if docstrings is None:
docstrings = ""
with open(PROJECT_PATHS.readme.as_posix(), "w") as file:
file.write(docstrings)
if git_add:
terminal_do_command(f"git add {PROJECT_PATHS.readme}", cwd=PROJECT_PATHS.root.as_posix())