Post-production of pages
======================================

## Introduction

Within mkdocs-macros, we call **post-production** all actions that follow
the definition of macros, with the `define_env(env)` hook.

They are of three types of post-production:

1. [Modifying markdown pages (either before or after macro rendering)](#markdown-pages)
2. [Influencing the HTML pages generated by mkdocs](#html-pages)
3. [Performing operations after mkdocs has completely executed](#post-build-files)

## Markdown pages (headers or footers, etc.)
### Introduction
_From version 0.5.2_

!!! Warning "_From version 1.0.0_"
    
    The `env.raw_markdown` attribute is deprecated. Use `env.markdown` instead.


There are specific cases where you want your module code to be able to modify
the markdown code of a **page, without using macros**.
One typical example is when you wish to alter the pages generated
e.g. by adding footer information at the bottom of each page,
or header information at the top of each page, or any other operation..

The proper time to do that, would be 
**before or after** the macros (Jinja2 directives) have been processed.

!!! Note "Technical note: a limitation of the macros mechanism"
    The `define_env()` function operates at the time when MkDocs prepares the
    configuration of website (the `on_config()` event). This is a 
    [global event](https://www.mkdocs.org/user-guide/plugins/#global-events), i.e. any change made at this point will affect the whole
    website. 

    The limitation is that the `define_env()` function is
    "aware" of the general configuration, not of the content of single pages.
    
    **True**,
    it allows you to declare **macros** which will be interpreted later,
    for each page (on the `on_page()` event). **But** it won't
    allow you to modify pages outside of that mechanism.


To act on such cases that vary with each markdown page (and depend on
each page, not on the general configuration), 
you may use the two hooks in your module, which are executed
 _before_ the markdown is actually rendered:

1. `on_pre_page_macros(env)` : just ** before ** the macros are rendered
   on a specific page (macros are still present).
   At this stage you can still insert text that contains macros.
2. `on_post_page_macros(env)` : just ** after ** the macros are rendered
   on a specific page (macros have turned into their markdown equivalent).
   At this stage you can insert 

You can access the markdown through the `env.markdown` property (string).




### Application to headers or footers

!!! Warning "_From version 1.0.0_"
    
    `on_pre_page_macros(env)` works as shown
    only from that version.

    For lower versions, in `on_post_page_macros(env)`
    replace `env.raw_markdown` with `env.markdown`
    

Headers or footers can be added before or after the rendering process.
If you add, e.g. a footer _before_ the rendering process, 
the macros inserted will be interpreted.

You may a add a variable footer _after_ the rendering process, 
but in that case you must use the Python syntax of the module to calculate
the values that go in each page footer.

!!! Note "Availability of `env.markdown`"
    `env.markdown` is accessible only at this stage of the production
    process (page production). It is _not_
    available in the `define_env()` hook, which is executed
    before the production of the pages has started.



If you wish to add a footer, you have the choice between:

- using macros (before rendering):

```python
def on_pre_page_macros(env):
    """
    Actions to be done before macro interpretation,
    when the macros have not yet been rendered.
    """
    footer = "\n\n{{ page.title }}"
    env.markdown += footer
```

- or doing it programmatically:

```python
def on_post_page_macros(env):
    """
    Actions to be done after macro interpretation,
    when the macros have been rendered
    This will add a (Markdown or HTML) footer -- produced by Python.
    """
    footer = "f'\n\n'{env.page.title}"
    env.markdown += footer
```


If you wish to use jinja2 directives, do it
it _before_ rendering:

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


### Technical Notes 

#### Time of execution

`on_pre_page_macros(env)` and `on_post_page_macros(env)` are executed by the [`on_page_markdown()` event of MkDocs](https://www.mkdocs.org/user-guide/plugins/#on_page_markdown):

- ** before** the rendering the page
- ** before or after** interpretation of the macros, respectively

They operates on all pages, page by page.

!!! Tip "Use of global variables"
    To facilitate the communication within your module
    between `define_env()` and 
    `on_page_markdown()` you may want to define **global variables**.
    For a refresher on this, 
    [see the summary on W3 Schools](https://www.w3schools.com/python/gloss_python_global_variables.asp). 

#### Content and availability of attributes
the `env. markdown` and `env.page` attributes are available
 only from the point of `on_pre_page_macros()` on. 
Thay are **not** available for the `define_env(env)` hook.

env.markdown contains the markdown of the page, before rendering
(in `on_pre_page_macros()`) or after rendering (in `on_post_page_macros()`))

env.page contains notably the following information:

| Attribute       | Value                                                                                                                             |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `title`         | title of the page                                                                                                                 |
| `abs_url`       | the absolute url of the page from the top of the hierarchy                                                                        |
| `canonical_url` | the complete url of the page (typically with `https://...`)                                                                       |
| `markdown`      | the whole markdown code (before or after rendering). |
| `meta`          | the meta data dictionary, as updated (typically) from the YAML header.                                                            |

---

env.page is a copy so modifying it won't affect the rendering process
(no black magic allowed).






## Html pages

### Introduction
It is possible to influence to some degree, the production of html files,
mostly by acting on templates.

### Trickling values into the mkdocs theme 
You may want to programmatically add some meta values to a page,
which should be forwarded to the
[HTML theme used by mkdocs](https://www.mkdocs.org/dev-guide/themes/) 
(which has its own, distinct Jinja2 engine).

This is called **trickling**
(values are trickled from the mkdocs-macros environment of variables
to the html page).

!!! Warning "Do not confuse "
    Note that that the **Jinja2** engine that operates
    on a mkdocs theme's pages is **completely distinct from the mkdocs-macros Jinja2 engine**, which is used to render macros
    on Markdown pages. The rendering of template variables
    occurs at a later stage of the
    page production process.


For example you'd want to be able to always have a value for this:

```HTML
<meta name="description" content="{{ page.meta.description }}" />
```



Normally [metadata would be defined in the YAML header of the markdown page](https://www.mkdocs.org/user-guide/writing-your-docs/#meta-data):

```YAML
---
title: my title
description: This is a description
---
```

!!! Note "Issue"
    **But supposing this was not the case ?** Or supposing you want
    to check or alter that information?



```python
def on_post_page_macros(env):
    """
    Actions to be done after macro interpretation,
    when the markdown is already generated
    """
    # This information will get carried into the HTML template.
    env.page.meta['description'] = ...

```




## Post-build files

_From version 0.5_

### Introduction

Sometimes, you want your Python code to add
some files to the HTML website that
MkDocs has just produced (after MkDoc's usual production workflow).

These could be:

- an extra HTML page
- an additional or updated image
- a RSS feed
- a form processor (written for example in the php language)
- ....

!!! Tip
    The logical idea is to add files to the site (HTML) directory,
    which is given by `env.conf['site_dir']`.

!!! Note "Beware the of the 'disappeared file' trap"

    One problem will occur if you attempt to add files to the site directory
    from within the `define_env()` function in your macro module.

    **The file will be created, but nevertheless it is going to "disappear".**

    The reason is that the code of `define_env()` is executed during the 
    `on_config` event of MkDocs; **and you can expect the site directory
    to be wiped out later, during the build phase (which produces
    the HTML files)**. So, of course, the files you
    just created will be deleted.


### Post-Build Actions

The solution to do that, is to perform those additions
as **post-build** actions (i.e. executed with `on_post_build` event).

Here is an example. Suppose you want to add a special file (e.g. HTML).

```Python
import os
MY_FILENAME = 'foo.html'
my_HTML = None

def define_env(env):
    "Definition of the module"

    # put here your HTML content
    my_HTML = ......


def on_post_build(env):
    "Post-build actions"

    site_dir = env.conf['site_dir']
    file_path = os.path.join(site_dir, MY_FILENAME)
    with open(file_path, 'w') as f:
        f.write(my_HTML)
```

The mkdocs-macros plugin will pick up that function and execute it during
as on `on_post_build()` action.

!!! Warning "Argument of `on_post_build()`"
    In this case, the argument is `env` (as for `define_env()`);
    it is **not**
    `config` as in the `on_post_build()` method in an MkDocs plugin.

    If you want to get the plugin's arguments, you can find them in the
    `env.conf` dictionary.

!!! Note "Global variables"
    To facilitate the communication between `define_env()` and 
    `on_post_build` you may want to define **global variables** within
    your module (in this example: `MY_FILENAME` and `my_HTML`).

!!! Warning
    Do not forget that any variable assigned for the first time
    within a function is _by default_
    a **local** variable: its content will be lost once the function
    is fully executed.

    In the example above, `my_HTML` **must** appear in the global definitions;
    which is why it was assigned an empty value.

    
