cnmkdocs.py

This module makes documentation for a project using MkDocs. It uses the project's source files and the config file "mkdocs.yml" to create MarkDown files in the "docs" folder. It then builds the html file structure in the "site" folder. It uses the "gh-deploy" program to publish the site to a remote-only branch. It then instructs GitHub Pages to auto-publish your docs at <username>.github.io/<repo_name> from that branch. As much code/settings/constants as can be are reused from conf.py.

CNMkDocs

A class to handle making and baking documentation

Source code in cnlib/cnmkdocs.py
class CNMkDocs:
    """
    A class to handle making and baking documentation
    """

    # --------------------------------------------------------------------------
    # Class constants
    # --------------------------------------------------------------------------

    # cmd for mkdocs
    # NB: format params are path to pp, path to pp venv, and path to project
    S_CMD_DOC_BUILD = ". {}/bin/activate;cd {};mkdocs build"
    # cmd for mkdocs
    # NB: format params are path to pp, path to pp venv, and path to project
    S_CMD_DOC_DEPLOY = ". {}/bin/activate;cd {};mkdocs gh-deploy"

    # file ext for in/out
    S_EXT_IN = ".py"
    S_EXT_OUT = ".md"

    # default to include mkdocstrings content in .md file
    # NB: format params are file name and formatted pkg name, done in make_docs
    S_DEF_FILE = "# {}\n::: {}"

    # default encoding
    S_ENCODING = "UTF-8"

    # --------------------------------------------------------------------------
    # Class methods
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # Initialize the new object
    # --------------------------------------------------------------------------
    def __init__(self):
        """
        Initialize the new object

        Initializes a new instance of the class, setting the default values
        of its properties, and any other code that needs to run to create a
        new object.
        """

        # NB: DO NOT CHANGE!!!
        self._index_name = "index.md"
        self._dir_img = "img"

    # --------------------------------------------------------------------------
    # Public functions
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # Make docs using mkdocs
    # --------------------------------------------------------------------------
    def make_docs(
        self,
        dir_prj,
        dir_docs,
        use_rm=False,
        use_api=False,
        lst_api_in=None,
        file_rm=None,
        dir_api_out=None,
        dir_img=None,
    ):
        """
        Docstring for make_docs

        :param self: Description
        :param dir_prj: Description
        :param use_rm: Description
        :param use_api: Description
        :param lst_api_in: Description
        :param file_rm: Description
        :param dir_api_out: Description
        :param dir_img: Description
        """

        # NB: mutable lists are only set once, passing an empty list does
        # NUSSING!!!
        # so we pass None to make sure the internal list gets reset
        if not lst_api_in:
            lst_api_in = []

        # ----------------------------------------------------------------------
        # check docs dir for exist

        # find docs dir
        dir_docs = Path(dir_prj) / dir_docs

        # no docs folder in template
        if not dir_docs.exists():

            # make dir
            dir_docs.mkdir(parents=True)

            # make img dir
            # NB: the name of this dir is hard-coded in mkdocs (AFAIK)
            # it is used exclusively to serve the favicon.ico
            # paths to readme images, etc, can use a different dir
            img_dir = dir_docs / self._dir_img
            img_dir.mkdir(parents=True)

        # ----------------------------------------------------------------------
        # make index

        # make empty or from readme or don't touch
        self._make_index(dir_prj, use_rm, file_rm, dir_docs)

        # ----------------------------------------------------------------------
        # make api

        # make new api or delete old
        self._make_api(dir_prj, use_api, lst_api_in, dir_api_out, dir_docs)

        # ----------------------------------------------------------------------
        # make img dir

        # dir_img is source (from project main)
        if dir_img:
            # make new img dir or combine
            dir_img_src = Path(dir_prj) / dir_img
            if dir_img_src.exists():
                dir_img_dst = dir_docs / dir_img
                shutil.copytree(dir_img_src, dir_img_dst, dirs_exist_ok=True)

    # --------------------------------------------------------------------------
    # Bake docs using mkdocs
    # --------------------------------------------------------------------------
    def build_docs(self, p_dir_pp_venv, p_dir_prj):
        """
        Bake docs using mkdocs

        Raises:
            cnlib.cnfunctions.CNRunError if bake fails

        Updates and deploys docs using mkdocs.
        """

        # ----------------------------------------------------------------------
        # build docs

        # format cmd using pdoc template dir, output dir, and start dir
        cmd_docs = self.S_CMD_DOC_BUILD.format(p_dir_pp_venv, p_dir_prj)

        # the command to run mkdocs
        try:
            cp = F.run(cmd_docs, shell=True, capture_output=True)
            return cp
        except F.CNRunError as e:
            raise e

    # --------------------------------------------------------------------------
    # Deploy docs using mkdocs
    # --------------------------------------------------------------------------
    def deploy_docs(self, p_dir_pp_venv, p_dir_prj):
        """
        Bake docs using mkdocs

        Raises:
            cnlib.cnfunctions.CNRunError if bake fails

        Updates and deploys docs using mkdocs.
        """

        # ----------------------------------------------------------------------
        # deploy docs

        # format cmd using pdoc template dir, output dir, and start dir
        cmd_docs = self.S_CMD_DOC_DEPLOY.format(p_dir_pp_venv, p_dir_prj)

        # the command to run mkdocs
        try:
            cp = F.run(cmd_docs, shell=True, capture_output=True)
            return cp
        except F.CNRunError as e:
            raise e

    # --------------------------------------------------------------------------
    # Private functions
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # Make the home file (index.md)
    # --------------------------------------------------------------------------
    def _make_index(self, dir_prj, use_rm, file_rm, dir_docs):
        """
        Make the home file (index.md)

        Make the index.md file from the README.
        """

        # first check if index exists
        dir_docs_out = Path(dir_prj) / dir_docs
        path_index = dir_docs_out / self._index_name

        # if not exist
        if not path_index.exists():

            # create empty file
            if not use_rm:
                with open(path_index, "w", encoding=self.S_ENCODING) as a_file:
                    a_file.write("Coming soon...")
                return

        # if exist
        else:

            # leave alone
            if not use_rm:
                return

        # ----------------------------------------------------------------------
        # make home page
        # NB: just copy readme.md to index.md
        if use_rm:

            # path to files
            readme_file = Path(dir_prj) / file_rm

            # read input file
            text = ""
            with open(readme_file, "r", encoding=self.S_ENCODING) as a_file:
                text = a_file.read()

            # write file
            with open(path_index, "w", encoding=self.S_ENCODING) as a_file:
                a_file.write(text)

    # --------------------------------------------------------------------------
    # Make the api files
    # --------------------------------------------------------------------------
    def _make_api(self, dir_prj, use_api, lst_api_in, dir_api_out, dir_docs):
        """
        Make the api files

        Make the documents using the specified parameters.
        """

        # sanity check
        if not dir_api_out:
            return

        # find api dir
        dir_prj = Path(dir_prj)
        dir_docs_out = dir_prj / dir_docs
        dir_api_out = dir_docs_out / dir_api_out

        # nuke it if it exists and we changed out mind
        if not use_api and dir_api_out.exists():
            shutil.rmtree(dir_api_out)
            return

        # ----------------------------------------------------------------------

        # gather list of full paths to .py files
        files_out = []

        for item in lst_api_in:

            dir_api = dir_prj / item

            # NB: root is a full path, dirs and files are relative to root
            for root, _root_dirs, root_files in dir_api.walk():

                # convert files into Paths
                files = [root / f for f in root_files]
                files = [
                    f
                    for f in files
                    if f.suffix.lower() == self.S_EXT_IN.lower()
                ]

                # for each file item
                for item in files:
                    files_out.append(item)

        # ----------------------------------------------------------------------
        # make structure

        # nuke / remake the api folder
        if dir_api_out.exists():

            # delete and recreate dir
            shutil.rmtree(dir_api_out)
            dir_api_out.mkdir(parents=True)

        # for each py file
        for f in files_out:

            # make a parent folder in docs (goes in nav bar)
            # NB: basically we find every .py file and get its path relative to
            # project dir
            # then we make a folder with the same relative path, but rel to docs
            # dir
            path_rel = f.relative_to(dir_prj)
            path_doc = dir_api_out / path_rel.parent
            path_doc.mkdir(parents=True, exist_ok=True)

            # create a default file
            # NB: just swap ".py" ext for ".md"
            file_md = path_doc / Path(str(f.stem) + self.S_EXT_OUT)

            # fix rel path into package dot notation
            s_parts = ".".join(path_rel.parts)
            if s_parts.endswith(self.S_EXT_IN):
                s_parts = s_parts.removesuffix(self.S_EXT_IN)

            # format contents of file
            file_fmt = self.S_DEF_FILE.format(f.name, s_parts)
            with open(file_md, "w", encoding=self.S_ENCODING) as a_file:
                a_file.write(file_fmt)

__init__()

Initialize the new object

Initializes a new instance of the class, setting the default values of its properties, and any other code that needs to run to create a new object.

Source code in cnlib/cnmkdocs.py
def __init__(self):
    """
    Initialize the new object

    Initializes a new instance of the class, setting the default values
    of its properties, and any other code that needs to run to create a
    new object.
    """

    # NB: DO NOT CHANGE!!!
    self._index_name = "index.md"
    self._dir_img = "img"

build_docs(p_dir_pp_venv, p_dir_prj)

Bake docs using mkdocs

Updates and deploys docs using mkdocs.

Source code in cnlib/cnmkdocs.py
def build_docs(self, p_dir_pp_venv, p_dir_prj):
    """
    Bake docs using mkdocs

    Raises:
        cnlib.cnfunctions.CNRunError if bake fails

    Updates and deploys docs using mkdocs.
    """

    # ----------------------------------------------------------------------
    # build docs

    # format cmd using pdoc template dir, output dir, and start dir
    cmd_docs = self.S_CMD_DOC_BUILD.format(p_dir_pp_venv, p_dir_prj)

    # the command to run mkdocs
    try:
        cp = F.run(cmd_docs, shell=True, capture_output=True)
        return cp
    except F.CNRunError as e:
        raise e

deploy_docs(p_dir_pp_venv, p_dir_prj)

Bake docs using mkdocs

Updates and deploys docs using mkdocs.

Source code in cnlib/cnmkdocs.py
def deploy_docs(self, p_dir_pp_venv, p_dir_prj):
    """
    Bake docs using mkdocs

    Raises:
        cnlib.cnfunctions.CNRunError if bake fails

    Updates and deploys docs using mkdocs.
    """

    # ----------------------------------------------------------------------
    # deploy docs

    # format cmd using pdoc template dir, output dir, and start dir
    cmd_docs = self.S_CMD_DOC_DEPLOY.format(p_dir_pp_venv, p_dir_prj)

    # the command to run mkdocs
    try:
        cp = F.run(cmd_docs, shell=True, capture_output=True)
        return cp
    except F.CNRunError as e:
        raise e

make_docs(dir_prj, dir_docs, use_rm=False, use_api=False, lst_api_in=None, file_rm=None, dir_api_out=None, dir_img=None)

Docstring for make_docs

:param self: Description :param dir_prj: Description :param use_rm: Description :param use_api: Description :param lst_api_in: Description :param file_rm: Description :param dir_api_out: Description :param dir_img: Description

Source code in cnlib/cnmkdocs.py
def make_docs(
    self,
    dir_prj,
    dir_docs,
    use_rm=False,
    use_api=False,
    lst_api_in=None,
    file_rm=None,
    dir_api_out=None,
    dir_img=None,
):
    """
    Docstring for make_docs

    :param self: Description
    :param dir_prj: Description
    :param use_rm: Description
    :param use_api: Description
    :param lst_api_in: Description
    :param file_rm: Description
    :param dir_api_out: Description
    :param dir_img: Description
    """

    # NB: mutable lists are only set once, passing an empty list does
    # NUSSING!!!
    # so we pass None to make sure the internal list gets reset
    if not lst_api_in:
        lst_api_in = []

    # ----------------------------------------------------------------------
    # check docs dir for exist

    # find docs dir
    dir_docs = Path(dir_prj) / dir_docs

    # no docs folder in template
    if not dir_docs.exists():

        # make dir
        dir_docs.mkdir(parents=True)

        # make img dir
        # NB: the name of this dir is hard-coded in mkdocs (AFAIK)
        # it is used exclusively to serve the favicon.ico
        # paths to readme images, etc, can use a different dir
        img_dir = dir_docs / self._dir_img
        img_dir.mkdir(parents=True)

    # ----------------------------------------------------------------------
    # make index

    # make empty or from readme or don't touch
    self._make_index(dir_prj, use_rm, file_rm, dir_docs)

    # ----------------------------------------------------------------------
    # make api

    # make new api or delete old
    self._make_api(dir_prj, use_api, lst_api_in, dir_api_out, dir_docs)

    # ----------------------------------------------------------------------
    # make img dir

    # dir_img is source (from project main)
    if dir_img:
        # make new img dir or combine
        dir_img_src = Path(dir_prj) / dir_img
        if dir_img_src.exists():
            dir_img_dst = dir_docs / dir_img
            shutil.copytree(dir_img_src, dir_img_dst, dirs_exist_ok=True)