fralau / mkdocs-macros-plugin

Create richer and more beautiful pages in MkDocs, by using variables and calls to macros in the markdown code.
https://mkdocs-macros-plugin.readthedocs.io
Other
323 stars 50 forks source link

How to declare a variable, which can be read by the theme, too? #75

Closed honzajavorek closed 3 years ago

honzajavorek commented 3 years ago

I've read through all the docs, but I'm still unclear about one thing and any guidance would be very appreciated. Let's say I want to dynamically declare a variable, which would be accessible in both the macros and the MkDocs' Jinja2, because I want to use it in the HTML template of the theme, too. Normally, this would be set in extras: in the YAML config, I suppose, but as it's dynamic, it cannot be. As an example, I'll choose now.

The now is available out of the box with macros, that's nice. But let's say I want to use it in headers or footers, too, in my theme. Can I use macros to (re)define it somehow so it's available even there? I tried to modify env.conf, env.variables etc., but it seems "it's too late" at that moment. If I was writing my own plugin, I'd do something like this:

import arrow

def on_env(env, config, files):
    env.globals.update(dict(
        now=arrow.utcnow(),
        handbook_release_at=arrow.get(2020, 9, 1),
    ))

Such variables would trickle down to both the theme and the pages/macros. At least I think. What would be the correct way to do this in macros, so that I don't need to introduce my own plugin? Thanks!

fralau commented 3 years ago

In principle variables created are to be used in the markdown files.

You can of course create variables from within the python module. The most intuitive way is by adding entries in the env.variables dictionary (see more on this).

Mkdoc's HTML templates are another thing (it's a completely distinct jinja2 engine, which comes out of the box with mkdocs). This templating engine uses the metadata of the markdown page to enrich the HTML code generated.

You can, however, use the python module to create or update page metadata by updating the enva.page.meta dictionary.

Caution: This cannot be done with the standard define_env() function, since that function is executed at a time of the building process, where the pages have not been generated yet (and the page variable is not available yet).

The proper way to generate metadata is described in a specific paragraph.. There are two functions, on_pre_page_macros() and on_post_page_macros() which have access (at a later stage) to the env.page.meta dictionary. It's not particularly difficult to do.

You can perfectly share information between define_env() and these other functions, typically through Python variables that are global to the module.

That would be the general way to trickle information from the macro environment to the jinja2/HTML template.

Let me know if this helps?

honzajavorek commented 3 years ago

Thank you, reading your response, it sounds exactly like what I'm looking for. I learned the mechanics you're writing about in the docs, but I guess I was missing the page.meta piece as to what's the most appropriate place where to put the vars. I'll return and report back in a few days, after I try it out. At this point I think I have all the info I needed 👍

fralau commented 3 years ago

Thank you. I'm pleased when someone tells me I have provided the type of answer that they expected. 😄

honzajavorek commented 3 years ago

So I got back to this and am experimenting with what we discussed. I didn't want my main project's specifics to interfere with this particular issue, so I created this playground sample project from scratch:

https://github.com/honzajavorek/mkdocs-macros-playground

It's a minimal example with just one document and a very simple template. At the time of writing this comment I couldn't get it work with modifying page.meta in the on_...() function:

Screenshot 2021-04-08 at 15 50 05

The current code of main.py is:

def define_env(env):
    env.variables['content_variable'] = 'works!'

def on_pre_page_macros(env):
    env.page.meta['content_and_template_variable'] = 'works!'

I'll keep experimenting and if I come up with a solution or have a progress, I'll report back here. You can browse the repo to see the context and all other files. Any ideas how to get it work would be highly appreciated.

honzajavorek commented 3 years ago

Okay, I played around with it and figured it out! One mistake I was doing was expecting the template will have the variables set just under their names, but they need to be extracted from the config and page data structures. To my current knowledge, one can dynamically (in Python code) set a variable to both the Markdown content and the theme template in at least two ways:

Both are demonstrated in the earlier mentioned repo I created exactly for this purpose. It generates the following page:

Screenshot 2021-04-08 at 16 43 33

For future reference, I'll share the bits of code here, too:

dynamic_extra_var = 'works!'

def define_env(env):
    env.variables['dynamic_extra_var'] = dynamic_extra_var
    env.variables['dynamic_macros_var'] = 'works!'

def on_pre_page_macros(env):
    env.conf['extra']['dynamic_extra_var'] = dynamic_extra_var
    env.page.meta['dynamic_meta_var'] = 'works!'

Modifying extra only works for the theme, so the same variable needs to be sent to macros again in a standard way. Maybe there's an order of assignments, which would prevent this, but I didn't experiment further to verify. Compared to this, meta is sufficient to modify only once, so that approach seems like a better way to go.

In the document, variables can be referenced directly by their names:

---
static_meta_var: 'works!'
---

# Magnificent Title

This is in the Markdown content enhanced with the Macros plugin. Static means it can be edited by human in a file:

- Printing `static_extra_var`: {{ static_extra_var }}
- Printing `static_meta_var`: {{ static_meta_var }}

Dynamic means it's set by a Python code:

- Printing `dynamic_macros_var`: {{ dynamic_macros_var }}
- Printing `dynamic_extra_var`: {{ dynamic_extra_var }}
- Printing `dynamic_meta_var`: {{ dynamic_meta_var }}

In the theme, variables need to be extracted from the config and page objects:

{{ page.title }}
<hr>
{{ page.content }}
<hr>
<p>
  Now this is in the theme template, that means MkDocs's very own Jinja2.
</p>
<ul>
  <li>Printing <code>static_extra_var</code>: {{ config.extra.static_extra_var|default('DOES NOT WORK') }}</li>
  <li>Printing <code>static_meta_var</code>: {{ page.meta.static_meta_var|default('DOES NOT WORK') }}</li>
  <li>Printing <code>dynamic_extra_var</code>: {{ config.extra.dynamic_extra_var|default('DOES NOT WORK') }}</li>
  <li>Printing <code>dynamic_meta_var</code>: {{ page.meta.dynamic_meta_var|default('DOES NOT WORK') }}</li>
</ul>