Skip to content

chore: Automatically generate example format tabs#4959

Merged
bradenhilton merged 1 commit intotwpayne:masterfrom
bradenhilton:examples
Apr 10, 2026
Merged

chore: Automatically generate example format tabs#4959
bradenhilton merged 1 commit intotwpayne:masterfrom
bradenhilton:examples

Conversation

@bradenhilton
Copy link
Copy Markdown
Collaborator

@bradenhilton bradenhilton commented Mar 15, 2026

Fixes #4954

@bradenhilton
Copy link
Copy Markdown
Collaborator Author

Just to clarify, I'm waiting for a bit of feedback on this (whenever anyone has time) before I proceed any further.

As always, there's no rush.

Copy link
Copy Markdown
Owner

@twpayne twpayne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks excellent to me. I particularly like that this is done inside the material-mkdocs framework.

Another nice feature is that when you choose the format, all examples in the same page change to use that format.

Great work @bradenhilton!

Comment thread assets/chezmoi.io/docs/hooks.py Outdated
Comment thread assets/chezmoi.io/docs/hooks.py Outdated
Comment thread assets/chezmoi.io/mkdocs.yml
@bradenhilton
Copy link
Copy Markdown
Collaborator Author

I've decided to not convert any blocks that contain template syntax, since tomllib cannot parse them as is.

It's non-trivial to manipulate the TOML text to work around this if the TOML is comprised of sections with template syntax between them, or if it contains sequences like {{ ... | toToml }}. One particular problem area would be any examples of templated externals.

At a minimum, you would probably need to do something like:

  1. Split the text on template lines, so you get each TOML section separately
  2. Store the template lines somewhere, along with some kind of index so you know where they were originally
  3. Parse and convert each TOML section individually
  4. Put the template lines back in-between each section
  5. (optional) Use a ```text fence instead to prevent ugly highlighting on the syntax "errors"

(there's probably a smarter/more elegant way to do this (perhaps a way to do it in a single pass or something), but the idea is essentially the same)

It probably can be solved, but in these cases it's probably far easier to just manually write the examples, especially now that the basic examples are done automatically.

Also, parsing most TOML blocks acts like a linter of sorts. I actually found a subtle quoting error on one of the blocks because it wouldn't parse correctly.

@KapJI
Copy link
Copy Markdown
Contributor

KapJI commented Mar 16, 2026

It can enforce manual conversions if it can't be done automatically, so we can make sure all examples are available in all versions.

@bradenhilton
Copy link
Copy Markdown
Collaborator Author

I've made a first pass at changing examples.

I searched the site directory for ```toml to find them.

I also added some temporary code that outputs all modified markdown files to an examples_preview directory at the root, for convenience in reviewing. I'll remove it before merging.

Comment thread assets/chezmoi.io/docs/hooks.py Outdated
Comment on lines +91 to +92
# We allow capturing *.toml.tmpl filename titles but do not support
# converting examples containing any template syntax.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably update this to clarify that things like template syntax within values is fine, but at the moment we don't support things like template syntax around or in-between blocks of TOML

Comment thread assets/chezmoi.io/docs/user-guide/templating.md Outdated
Comment thread assets/chezmoi.io/docs/user-guide/include-files-from-elsewhere.md Outdated
Comment thread assets/chezmoi.io/docs/hooks.py Outdated
Comment thread assets/chezmoi.io/docs/hooks.py Outdated
return textwrap.indent(examples, indent)

new_md = example_pattern.sub(replace, markdown)
if new_md != markdown:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be useful for debugging, hide behind env var?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree, but it's outside the scope of this PR, and temporary environment variables don't have great ergonomics on Windows.

Perhaps we can integrate it into taskipy (or some other task runner), but I won't add it to this PR.

cc @halostatue, who is more familiar with taskipy

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to investigate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could maybe look into replacing Make+taskipy with Just or Task at some point

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get your "outside the scope" argument. This is debug feature which needs to be disabled or removed before merging. I think it makes sense to keep it and activate by CLI flag (e.g. --debug) or env var.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this feature will likely require using an env var directly inside a task runner, as I alluded to in #4959 (comment). The feature is also potentially useful for other kinds of transformations, not just the one specific to this PR.

If it can't be done with taskipy, that means we need to potentially switch to a different task runner (if we can find one that works). I don't want to do either that or manual handling of an env var in this PR because I am currently using Windows, and temporary env vars for a single command are beyond tedious unless you use a sane operating system.

The debug output code is temporary and will be removed before this PR is merged.

Comment thread assets/chezmoi.io/docs/hooks.py Outdated
Comment thread assets/chezmoi.io/docs/hooks.py Outdated
Comment thread assets/chezmoi.io/docs/hooks.py Outdated
return textwrap.indent(examples, indent)

new_md = example_pattern.sub(replace, markdown)
if new_md != markdown:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to investigate.

yaml_obj.dump(data, yaml_stream)
yaml_text = yaml_stream.getvalue()

json_text = json.dumps(data, indent=4)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tomllib.loads may return non-serializable type:

text = tomllib.loads("updated = 2026-03-27")
json.dumps(text)
# TypeError: Object of type datetime is not JSON serializable

It'd be nice to handle such error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TypeError is sufficient, no?

The primary point of this change is to make it easier and less error-prone to provide equivalent examples in multiple formats. If any example contains any types that are not serializable in a different format, it isn't suitable for this transformation.

Tom also said it's fine for errors to blow up in #4959 (comment).

elif FENCE_CLOSE_RX.match(line):
state = MarkdownTransformState.IN_BLOCK
else:
if line.lstrip().startswith('{{'):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can template be in the middle of the line?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe only template syntax within strings will parse correctly (and also work in the other formats). I am aware that the current approach is naive.

Perhaps it would be better to pull the TOML parsing/validation out into a separate function that is called in the loop, rather than doing it inside the function that also creates the tabs.

Comment thread assets/chezmoi.io/docs/hooks.py Outdated
return textwrap.indent(examples, indent)

new_md = example_pattern.sub(replace, markdown)
if new_md != markdown:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get your "outside the scope" argument. This is debug feature which needs to be disabled or removed before merging. I think it makes sense to keep it and activate by CLI flag (e.g. --debug) or env var.

Comment thread assets/chezmoi.io/docs/hooks.py Outdated
def on_page_markdown(markdown: str, page: Page, **kwargs) -> str:
lines = markdown.splitlines()

new_md = '\n'.join(transform_markdown(lines))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to add a training newline here.

]


class MarkdownTransformState(Enum):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much cleaner than old regexp!

Copy link
Copy Markdown
Owner

@twpayne twpayne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is excellent, thank you very much @bradenhilton for the work and @KapJI and @halostatue for the reviews.

One minor thing I noticed is that example configs don't get highlighted correctly when they're in !!! info boxes, but this is almost certainly an issue with mkdocs-material and doesn't need to be fixed here. The easy fix is probably to simply avoid using so many !!! info boxes.

Please tidy up the commit history and merge it when you feel that it's ready.

@bradenhilton bradenhilton merged commit d844fad into twpayne:master Apr 10, 2026
20 checks passed
@bradenhilton bradenhilton deleted the examples branch April 14, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documentation example improvements

4 participants