Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modifying page markdown before macro interpretation #158

Closed
hexus opened this issue Apr 3, 2023 · 7 comments
Closed

Modifying page markdown before macro interpretation #158

hexus opened this issue Apr 3, 2023 · 7 comments
Labels
fixed A fix has been submitted good first issue Good for newcomers info required Further information is requested useful tip A how-to, good to know

Comments

@hexus
Copy link

hexus commented Apr 3, 2023

Hello there!

First of all, thanks for such an excellent plugin for MkDocs. It makes it a breeze to use templating, and it's nice to have all of the features of Jinja available.

One thing that tripped me up today, however, was trying to achieve auto-appended macro-interpreted Markdown for every page. I certainly don't consider this a bug or anything, but I thought you might like to know about it as a potential use case.

I'm using Material for MkDocs, and I wanted to use a data-driven glossary from data that I include using your excellent include_yaml feature.

plugins:
  - macros:
      include_dir: includes
      include_yaml:
        - includes/glossary.yml

First approach

Originally, I'd tried to use the macros within a snippet via pymdownx.snippets's auto_append configuration, but these are simply included after your plugin runs (for "MkDocs reasons" unbeknownst to me):

markdown_extensions:
  - pymdownx.snippets:
      auto_append:
        - glossary.md
      base_path:
        - includes

We can assume here that includes/glossary.md is a Markdown file that contains Jinja templating, intended for interpretation by mkdocs-macro-plugin.

Second approach

After that, I looked into mkdocs-macros-plugin modules, with the context of the Advanced usage guide, and it seemed simple enough.

So I created a main.py and tried modifying the page markdown before macros are interpreted with the on_pre_page_macros() hook.

def define_env(env):
    "Adds a macro-interpreted footer to all pages"

def on_pre_page_macros(env):
    env.page.markdown += "\n\n{% include('glossary.md') %}"

This didn't work as I might have expected; the macro was not interpreted, or included on the page at all. 🤔

Final approach

Having observed the source code of the plugin, I understood why and realised I could work around it.

footer = ''

def define_env(env):
    "Adds a macro-interpreted footer to all pages"

def on_post_page_macros(env):
    global footer

    if not footer:
        footer = env.render(markdown="\n\n{% include('glossary.md') %}")

    env.raw_markdown += footer

The above works, but it does feel a little hacky. 🤓

Potential improvements for mkdocs-macros-plugin

I can see that my confusion was a due to my mismatched expectation from the Content and availability of env.page documentation and the following approach in the plugin:

# execute the pre-macro functions in the various modules
for func in self.pre_macro_functions:
	func(self)
# render the macros
self._raw_markdown = self.render(markdown)
# execute the post-macro functions in the various modules
for func in self.post_macro_functions:
	func(self)
return self.raw_markdown

So, perhaps it could take into account the resulting env.page.markdown value, or just set env.markdown before the calls to the pre_macro_functions, and then use its resulting value for the calls to the post_macro_functions.

Something like this:

# execute the pre-macro functions in the various modules
self.markdown = markdown;
for func in self.pre_macro_functions:
	func(self)
# render the macros
self._raw_markdown = self.render(self.markdown)
# execute the post-macro functions in the various modules
for func in self.post_macro_functions:
	func(self)
return self.raw_markdown

Alternatively, a nice feature would be the ability to auto append or auto prepend files to each page, much like pymdownx.snippets's auto_append configuration.

The ideal configuration scenario for me would be to avoid maintaining my own Python entirely and just configure a number of auto-appended or auto-prepended Markdown files from the include directory.

For example:

plugins:
  - macros:
      auto_prepend:
        - header.md
      auto_append:
        - footer.md
      include_dir: includes
      include_yaml:
        - includes/glossary.yml

I hope this was insightful, and I hope I explained clearly enough.

Cheers! 🍻

@github-actions
Copy link

github-actions bot commented Apr 3, 2023

Welcome to this project and thank you!' first issue

@fralau fralau added good first issue Good for newcomers useful tip A how-to, good to know labels Apr 22, 2023
@fralau
Copy link
Owner

fralau commented Apr 22, 2023

That's very interesting.

@fralau
Copy link
Owner

fralau commented Apr 23, 2023

You don't have to use the hack! That works to add a footer:

def on_post_page_macros(env):
    "After macros were executed"
    # This will add a (Markdown or HTML) footer
    footer = '\n'.join(
        ['', '# Added Footer (post-rendering)', 'Name of the page is _%s_' % env.page.title])
    env.raw_markdown += footer

Keep in mind that Python code is executed when it is called, so it is doing the conversion.

@fralau fralau added the planned This is planned for correction label Apr 23, 2023
@hexus
Copy link
Author

hexus commented Apr 23, 2023

In my case, I did have to use the workaround due to the need for macros in a footer: {% include('glossary.md') %}

I at least used a global variable to memoize the rendered HTML, making sure that the macro was only resolved once per build, rather than once per page per build. Convenient that Markdown can contain any HTML!

@fralau fralau closed this as completed in d4568b3 Apr 23, 2023
@fralau fralau reopened this Apr 23, 2023
@fralau
Copy link
Owner

fralau commented Apr 23, 2023

I implemented your suggestion and that should work now:

def on_pre_page_macros(env):
    """
    Actions to be done before macro interpretation,
    """ 
    footer = "\n\n{% include('glossary.md') %}"
    env.markdown += footer

Could I ask you to test?

@fralau fralau added info required Further information is requested fixed A fix has been submitted and removed planned This is planned for correction labels Apr 23, 2023
@hexus
Copy link
Author

hexus commented Apr 25, 2023

Yep, this works a treat. Super simple main.py now.

def on_pre_page_macros(env):
    env.markdown += "\n\n{% include('glossary.md') %}"

Thanks, @fralau! 🍻

Perhaps some day I'll prepare a PR for append and prepend configs. 🤓

@hexus hexus closed this as completed Apr 25, 2023
@fralau
Copy link
Owner

fralau commented Apr 25, 2023

@hexus Good idea for the PR, thanks! 👍

First open an issue with a concise statement of the problem, and the solution you intend to implement. In that way, you could then go into the development with the assurance that it will work. And we will have documentation in the future, for reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fixed A fix has been submitted good first issue Good for newcomers info required Further information is requested useful tip A how-to, good to know
Projects
None yet
Development

No branches or pull requests

2 participants