Module portray.render
Defines how to render the current project and project_config using the
included documentation generation utilities.
View Source
"""Defines how to render the current project and project_config using the
included documentation generation utilities.
"""
import os
import shutil
import sys
import tempfile
from contextlib import contextmanager
from glob import glob
from typing import Dict, Iterator, Tuple
import mkdocs.config as mkdocs_config
import mkdocs.exceptions as _mkdocs_exceptions
from mkdocs.commands.build import build as mkdocs_build
from mkdocs.config.defaults import get_schema as mkdocs_schema
from mkdocs.utils import is_markdown_file
from pdocs import as_markdown as pdocs_as_markdown
from yaspin import yaspin
from portray.exceptions import DocumentationAlreadyExists
NO_HOME_PAGE = """
# Nothing here
`portray` uses README.md as your projects home page.
It appears you do not yet have a README.md file created.
"""
def documentation(config: dict, overwrite: bool = False) -> None:
"""Renders the entire project given the project config into the config's
specified output directory.
Behind the scenes:
- A temporary directory is created and your code is copy and pasted there
- pdoc is ran over your code with the output sent into the temporary directory
as Markdown documents
- MkDocs is ran over all of your projects Markdown documents including those
generated py pdoc. MkDocs outputs an HTML representation to a new temporary
directory.
- The html temporary directory is copied into your specified output location
- Both temporary directories are deleted.
"""
if os.path.exists(config["output_dir"]):
if overwrite:
shutil.rmtree(config["output_dir"])
else:
raise DocumentationAlreadyExists(config["output_dir"])
with documentation_in_temp_folder(config) as (_, documentation_output):
shutil.copytree(documentation_output, config["output_dir"])
def pdocs(config: dict) -> None:
"""Render this project using the specified pdoc config passed into pdoc.
This rendering is from code definition to Markdown so that
it will be compatible with MkDocs.
"""
pdocs_as_markdown(**config)
def mkdocs(config: dict):
"""Render the project's associated Markdown documentation using the specified
MkDocs config passed into the MkDocs `build` command.
This rendering is from `.md` Markdown documents into HTML
"""
config_instance = _mkdocs_config(config)
return mkdocs_build(config_instance)
@contextmanager
def documentation_in_temp_folder(config: dict) -> Iterator[Tuple[str, str]]:
"""Build documentation within a temp folder, returning that folder name before it is deleted."""
if config["append_directory_to_python_path"] and not config["directory"] in sys.path:
sys.path.append(config["directory"])
with tempfile.TemporaryDirectory() as input_dir:
input_dir = os.path.join(input_dir, "input")
os.mkdir(input_dir)
with tempfile.TemporaryDirectory() as temp_output_dir:
with yaspin(
text="Copying source documentation to temporary compilation directory"
) as spinner:
for root_file in os.listdir(config["directory"]):
root_file_absolute = os.path.join(config["directory"], root_file)
if os.path.isfile(root_file_absolute) and is_markdown_file(root_file_absolute):
shutil.copyfile(root_file_absolute, os.path.join(input_dir, root_file))
for source_directory in [config["docs_dir"]] + config["extra_dirs"]:
directory_absolute = os.path.join(config["directory"], source_directory)
if os.path.isdir(directory_absolute):
shutil.copytree(
directory_absolute, os.path.join(input_dir, source_directory)
)
spinner.ok("Done")
if "docs_dir" not in config["mkdocs"]:
config["mkdocs"]["docs_dir"] = input_dir
if "site_dir" not in config["mkdocs"]:
config["mkdocs"]["site_dir"] = temp_output_dir
if "nav" not in config["mkdocs"]:
nav = config["mkdocs"]["nav"] = []
root_docs = sorted(glob(os.path.join(input_dir, "*.md")))
readme_doc = os.path.join(input_dir, "README.md")
if readme_doc in root_docs:
root_docs.remove(readme_doc)
else:
with open(readme_doc, "w") as readme_doc_file:
readme_doc_file.write(NO_HOME_PAGE)
nav.append({"Home": "README.md"})
nav.extend(_doc(doc, input_dir, config) for doc in root_docs)
nav.extend(
_nested_docs(os.path.join(input_dir, config["docs_dir"]), input_dir, config)
)
else:
nav = config["mkdocs"]["nav"]
if nav:
index_nav = nav[0]
index_page: str = ""
if index_nav and isinstance(index_nav, dict):
index_page = tuple(index_nav.values())[0]
elif isinstance(index_nav, str): # pragma: no cover
index_page = index_nav
if index_page:
destination_index_page = os.path.join(input_dir, "index.md")
if (
index_page != "README.md"
and index_page != "index.md"
and not os.path.exists(destination_index_page)
):
shutil.copyfile(
os.path.join(input_dir, index_page), destination_index_page
)
if config["include_reference_documentation"]:
with yaspin(text="Auto generating reference documentation using pdocs") as spinner:
if "output_dir" not in config["pdocs"]:
config["pdocs"]["output_dir"] = os.path.join(input_dir, "reference")
pdocs(config["pdocs"])
reference_docs = _nested_docs(config["pdocs"]["output_dir"], input_dir, config)
nav.append({"Reference": reference_docs}) # type: ignore
spinner.ok("Done")
with yaspin(text="Rendering complete website from Markdown using MkDocs") as spinner:
mkdocs(config["mkdocs"])
spinner.ok("Done")
# remove any settings pointing to the temp dirs
if config["mkdocs"]["docs_dir"].startswith(input_dir):
del config["mkdocs"]["docs_dir"]
if config["mkdocs"]["site_dir"].startswith(temp_output_dir):
del config["mkdocs"]["site_dir"]
if config["pdocs"]["output_dir"].startswith(input_dir):
del config["pdocs"]["output_dir"]
if config["include_reference_documentation"]:
nav.pop()
yield input_dir, temp_output_dir
def _mkdocs_config(config: dict) -> mkdocs_config.Config:
config_instance = mkdocs_config.Config(schema=mkdocs_schema())
config_instance.load_dict(config)
errors, warnings = config_instance.validate()
if errors:
print(errors)
raise _mkdocs_exceptions.ConfigurationError(
f"Aborted with {len(errors)} Configuration Errors!"
)
elif config.get("strict", False) and warnings: # pragma: no cover
print(warnings)
raise _mkdocs_exceptions.ConfigurationError(
f"Aborted with {len(warnings)} Configuration Warnings in 'strict' mode!"
)
config_instance.config_file_path = config["config_file_path"]
return config_instance
def _nested_docs(directory: str, root_directory: str, config: dict) -> list:
nav = [
_doc(doc, root_directory, config) for doc in sorted(glob(os.path.join(directory, "*.md")))
]
nested_dirs = sorted(glob(os.path.join(directory, "*/")))
for nested_dir in nested_dirs:
if (
len(glob(os.path.join(nested_dir, "*.md")) + glob(os.path.join(nested_dir, "**/*.md")))
> 0
):
dir_nav = {
_label(nested_dir[:-1], config): _nested_docs(nested_dir, root_directory, config)
}
nav.append(dir_nav) # type: ignore
return nav
def _label(path: str, config: Dict) -> str:
label = os.path.basename(path)
if "." in label:
label = ".".join(label.split(".")[:-1])
label = label.replace("-", " ").replace("_", " ").title()
return config["labels"].get(label, label)
def _doc(path: str, root_path: str, config: dict) -> Dict[str, str]:
path = os.path.relpath(path, root_path)
return {_label(path, config): path}
Variables
NO_HOME_PAGE
Functions
documentation
def documentation(
config: dict,
overwrite: bool = False
) -> None
Renders the entire project given the project config into the config's
specified output directory.
Behind the scenes:
- A temporary directory is created and your code is copy and pasted there
- pdoc is ran over your code with the output sent into the temporary directory as Markdown documents
- MkDocs is ran over all of your projects Markdown documents including those generated py pdoc. MkDocs outputs an HTML representation to a new temporary directory.
- The html temporary directory is copied into your specified output location
- Both temporary directories are deleted.
View Source
def documentation(config: dict, overwrite: bool = False) -> None:
"""Renders the entire project given the project config into the config's
specified output directory.
Behind the scenes:
- A temporary directory is created and your code is copy and pasted there
- pdoc is ran over your code with the output sent into the temporary directory
as Markdown documents
- MkDocs is ran over all of your projects Markdown documents including those
generated py pdoc. MkDocs outputs an HTML representation to a new temporary
directory.
- The html temporary directory is copied into your specified output location
- Both temporary directories are deleted.
"""
if os.path.exists(config["output_dir"]):
if overwrite:
shutil.rmtree(config["output_dir"])
else:
raise DocumentationAlreadyExists(config["output_dir"])
with documentation_in_temp_folder(config) as (_, documentation_output):
shutil.copytree(documentation_output, config["output_dir"])
documentation_in_temp_folder
def documentation_in_temp_folder(
config: dict
) -> Iterator[Tuple[str, str]]
Build documentation within a temp folder, returning that folder name before it is deleted.
View Source
@contextmanager
def documentation_in_temp_folder(config: dict) -> Iterator[Tuple[str, str]]:
"""Build documentation within a temp folder, returning that folder name before it is deleted."""
if config["append_directory_to_python_path"] and not config["directory"] in sys.path:
sys.path.append(config["directory"])
with tempfile.TemporaryDirectory() as input_dir:
input_dir = os.path.join(input_dir, "input")
os.mkdir(input_dir)
with tempfile.TemporaryDirectory() as temp_output_dir:
with yaspin(
text="Copying source documentation to temporary compilation directory"
) as spinner:
for root_file in os.listdir(config["directory"]):
root_file_absolute = os.path.join(config["directory"], root_file)
if os.path.isfile(root_file_absolute) and is_markdown_file(root_file_absolute):
shutil.copyfile(root_file_absolute, os.path.join(input_dir, root_file))
for source_directory in [config["docs_dir"]] + config["extra_dirs"]:
directory_absolute = os.path.join(config["directory"], source_directory)
if os.path.isdir(directory_absolute):
shutil.copytree(
directory_absolute, os.path.join(input_dir, source_directory)
)
spinner.ok("Done")
if "docs_dir" not in config["mkdocs"]:
config["mkdocs"]["docs_dir"] = input_dir
if "site_dir" not in config["mkdocs"]:
config["mkdocs"]["site_dir"] = temp_output_dir
if "nav" not in config["mkdocs"]:
nav = config["mkdocs"]["nav"] = []
root_docs = sorted(glob(os.path.join(input_dir, "*.md")))
readme_doc = os.path.join(input_dir, "README.md")
if readme_doc in root_docs:
root_docs.remove(readme_doc)
else:
with open(readme_doc, "w") as readme_doc_file:
readme_doc_file.write(NO_HOME_PAGE)
nav.append({"Home": "README.md"})
nav.extend(_doc(doc, input_dir, config) for doc in root_docs)
nav.extend(
_nested_docs(os.path.join(input_dir, config["docs_dir"]), input_dir, config)
)
else:
nav = config["mkdocs"]["nav"]
if nav:
index_nav = nav[0]
index_page: str = ""
if index_nav and isinstance(index_nav, dict):
index_page = tuple(index_nav.values())[0]
elif isinstance(index_nav, str): # pragma: no cover
index_page = index_nav
if index_page:
destination_index_page = os.path.join(input_dir, "index.md")
if (
index_page != "README.md"
and index_page != "index.md"
and not os.path.exists(destination_index_page)
):
shutil.copyfile(
os.path.join(input_dir, index_page), destination_index_page
)
if config["include_reference_documentation"]:
with yaspin(text="Auto generating reference documentation using pdocs") as spinner:
if "output_dir" not in config["pdocs"]:
config["pdocs"]["output_dir"] = os.path.join(input_dir, "reference")
pdocs(config["pdocs"])
reference_docs = _nested_docs(config["pdocs"]["output_dir"], input_dir, config)
nav.append({"Reference": reference_docs}) # type: ignore
spinner.ok("Done")
with yaspin(text="Rendering complete website from Markdown using MkDocs") as spinner:
mkdocs(config["mkdocs"])
spinner.ok("Done")
# remove any settings pointing to the temp dirs
if config["mkdocs"]["docs_dir"].startswith(input_dir):
del config["mkdocs"]["docs_dir"]
if config["mkdocs"]["site_dir"].startswith(temp_output_dir):
del config["mkdocs"]["site_dir"]
if config["pdocs"]["output_dir"].startswith(input_dir):
del config["pdocs"]["output_dir"]
if config["include_reference_documentation"]:
nav.pop()
yield input_dir, temp_output_dir
mkdocs
def mkdocs(
config: dict
)
Render the project's associated Markdown documentation using the specified
MkDocs config passed into the MkDocs build
command.
This rendering is from .md
Markdown documents into HTML
View Source
def mkdocs(config: dict):
"""Render the project's associated Markdown documentation using the specified
MkDocs config passed into the MkDocs `build` command.
This rendering is from `.md` Markdown documents into HTML
"""
config_instance = _mkdocs_config(config)
return mkdocs_build(config_instance)
pdocs
def pdocs(
config: dict
) -> None
Render this project using the specified pdoc config passed into pdoc.
This rendering is from code definition to Markdown so that it will be compatible with MkDocs.
View Source
def pdocs(config: dict) -> None:
"""Render this project using the specified pdoc config passed into pdoc.
This rendering is from code definition to Markdown so that
it will be compatible with MkDocs.
"""
pdocs_as_markdown(**config)