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
doittask to executemkdocs
> 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
tonyfastmodulepython -m tonyfast tasks mkdocs
-
this command is invoked with
hatchin 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