Skip to content

doit integration - a mkdocs example¤

in the hunt for a computable blog, i've wanted to be able to reuse the code i write about in posts. one integration we are beginning to experiment with in this post is access to doit task specifications from posts. the tonyfast.dodo module integrates posts with tasks.

doit is a great tool for orchestrating commands that operate on files. to demonstrate the integration we'll compose a task to build a mkdocs site, and deal with some pain points in the configuration.

the mkdocs task¤

mkdocs integrations are identified when a project has the mkdocs.yml file. this file contains all the information for your site including the documents to build. for blog style content, adding a new post means updating the configuration file. my adhd brain has a tendency to forget the configuration bit. in this file, we wrote tools to infer the files that should be included in the configuration, we sort them, and place them back in the configuration file.

what follow is the incovation for mkdocs for the tonyfast site. in this document we:

  • define functions to find posts
  • define a doit task to execute mkdocs

> there is a mkdocs-material blog feature avaiable to insiders. when this is generally available a lot of this content will be moot.

the mkdocs doit task¤

primarily doit tasks are functions that begin with the prefix task_; task_mkdocs is the one task we define in this document.

find posts and build the mkdocs site

    def task_mkdocs():
        yield dict(name="toc", actions=[
            (set_nav, (pathlib.Path("tonyfast"), "*.ipynb", "mkdocs.yml"))
        ], targets=["mkdocs.yml"], uptodate=[False])
        yield dict(
            name="build",
            actions=["mkdocs build"],
            file_dep=["mkdocs.yml"],
            targets=["site/index.html"]
        )

finding and sorting the posts¤

find all the potential notebook posts

    def get_posts_from_dir(dir, glob="*.ipynb"):
        for file in dir.iterdir():
            if file.is_dir():
                if "checkpoints" not in file.name: yield from get_posts_from_dir(file, glob)
            elif file.suffix in {".ipynb"}:
                if (m := title.match(str(file.stem))): yield file, m

    def get_posts(dir, glob="*.ipynb"):
        return sorted((
            (x.relative_to(WHERE), y) for x, y in get_posts_from_dir(dir, glob)
        ), reverse=True, key=lambda x: x[1].group(*"ymd"))

format the ordered entries

    def get_toc(dir, glob="*.ipynb"):
        for x, y in list(get_posts(WHERE, glob)):
            yield " "* 4 + F"- {x}" 

we don't use yaml cause our mkdocs uses yaml tags. instead we put the nav at the end of the document then replace the default with the updated version.

replace the nav¤

    def get_nav(dir, glob="*.ipynb"):
        return "".join(re.split(
            "(notes\S*:\S*)",
            (WHERE.parent / "mkdocs.yml").read_text(),
            1
        )[:2] )+ "\n" + "\n".join(get_toc(dir, glob))

    import tonyfast, pathlib, yaml, re
    WHERE = pathlib.Path(tonyfast.__file__).parent
    title = re.compile("(?P<y>[0-9]{4})-(?P<m>[0-9]{1,2})-(?P<d>[0-9]{1,2})-(?P<t>.+)")

    def set_nav(dir, glob="*.ipynb", target=WHERE.parent / "mkdocs.yml"):
        pathlib.Path(target).write_text(get_nav(pathlib.Path(dir), glob))

invocation¤

  • the task is exposed in the tonyfast module

    python -m tonyfast tasks mkdocs

  • this command is invoked with hatch in a virtual environment using:

    hatch run docs:build

  • use with importnb -t

    if "__file__" not in locals():
        !python -m importnb -t 2022-12-18-mkdocs-task.ipynb info mkdocs
mkdocs

find posts and build the mkdocs site

status     : run
 * The task has no dependencies.

task_dep   : 
 - mkdocs:toc
 - mkdocs:build