Skip to content

Commit

Permalink
Add force_render_paths parameter in config file (#204)
Browse files Browse the repository at this point in the history
  - this is a mechanism that forces rendering of files
    for the case when `render_by_default` is set to `false`.
  - it uses a git-like syntax to define files to include
  • Loading branch information
Laurent Franceschetti committed Feb 19, 2024
1 parent ef51c7a commit 827943d
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 37 deletions.
5 changes: 3 additions & 2 deletions mkdocs_macros/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,9 @@ def render_file(filename):
SOURCE_FILE = os.path.join(SOURCE_DIR, filename)
with open(SOURCE_FILE) as f:
s = f.read()
# now we need to render the jinja2 directives:
return env.render(s)
# now we need to render the jinja2 directives,
# always rendering (to skip reasoning about page header)
return env.render(s, force_rendering=True)

@env.macro
def context(obj=env.variables):
Expand Down
99 changes: 75 additions & 24 deletions mkdocs_macros/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from jinja2 import (
Environment, FileSystemLoader, Undefined, DebugUndefined, StrictUndefined,
)
import pathspec

from mkdocs.config import config_options
from mkdocs.config.config_options import Type as PluginType
from mkdocs.plugins import BasePlugin
Expand Down Expand Up @@ -86,6 +88,11 @@ class MacrosPlugin(BasePlugin):
default=[])),
# How to render pages by default: yes (opt-out), no (opt-in)
('render_by_default', PluginType(bool, default=True)),
# Force the rendering of those directories and files
# Use Pathspec syntax (similar to gitignore)
# see: https://python-path-specification.readthedocs.io/en/stable/readme.html#tutorial
# this is relative to doc_dir
('force_render_paths', J2_STRING),
# Include directory for external files
# also works for {% include ....%}) and {% import ....%}):
('include_dir', J2_STRING),
Expand Down Expand Up @@ -333,8 +340,24 @@ def post_build_functions(self):
except AttributeError:
raise AttributeError("You called post_build_functions property "
"too early. Does not exist yet !")

def force_page_rendering(self, filename:str)->bool:
"""
Predicate: it defines whether the rendering of this page
filename must be forced
(because it is in the `force_render_paths` parameters).
# ----------------------------------
That parameterer is parsed in on_config() and used to define
`render_paths_spec`.
"""
try:
return self._render_paths_spec.match_file(filename)
except AttributeError:
raise AttributeError("You called the force_render() method "
"too early. Not initialized yet !")

# -----------------------s-----------
# load elements
# ----------------------------------

Expand Down Expand Up @@ -469,9 +492,17 @@ def _load_modules(self):
"module in '%s'." %
(local_module_name, self.project_dir))

def render(self, markdown: str):
def render(self, markdown: str, force_rendering:bool=False):
"""
Render a page through jinja2: it executes the macros
Render a page through jinja2: it executes the macros.
It keeps account of the `render_macros` variable
in the page's header to decide whether to actually
render or not (but you can force it).
Arguments
---------
- markdown: the markdown/HTML page (with the jinja2 macros)
- force_rendering: force the rendering anyway
Returns
-------
Expand All @@ -495,26 +526,32 @@ def render(self, markdown: str):
except KeyError as e:
# this is a premature rendering, no meta variables in the page
meta_variables = {}
# Warning this is ternary logique (True, False, None: nothing said)
ignore_macros = None
render_macros = None

if meta_variables:
# determine whether the page will be rendered or not
# the two formulations are accepted
ignore_macros = meta_variables.get('ignore_macros')
render_macros = meta_variables.get('render_macros')

if self.config['render_by_default']:
# opt-out: force of a page NOT to be interpreted,
opt_out = ignore_macros == True or render_macros == False
if opt_out:
return markdown

if force_rendering:
# [if force_render=True, it skips all the reasoning in the else]
pass
else:
# opt-in: force a page to be interpreted
opt_in = render_macros == True or ignore_macros == False
if not opt_in:
return markdown
# Warning this is ternary logic(True, False, None: nothing said)
ignore_macros = None # deprecated
render_macros = None

if meta_variables:
# determine whether the page will be rendered or not
# the two formulations are accepted
ignore_macros = meta_variables.get('ignore_macros')
render_macros = meta_variables.get('render_macros')

if self.config['render_by_default']:
# opt-out: force of a page NOT to be interpreted,
opt_out = ignore_macros == True or render_macros == False
if opt_out:
return markdown
else:
# opt-in: you must force a page to be interpreted
opt_in = render_macros == True or ignore_macros == False
if not opt_in:
return markdown

# Update the page with meta variables
# i.e. what's in the yaml header of the page
page_variables.update(meta_variables)
Expand Down Expand Up @@ -592,7 +629,15 @@ def on_config(self, config):
debug("Content of extra variables (config file):", extra)
if self.filters:
trace("Extra filters (module):", list(self.filters.keys()))


# Define the spec for the file paths whose rendering must be forced.
# It will be used by the force_page_rendering() predicate:
force_render_paths = self.config['force_render_paths']
self._render_paths_spec = pathspec.PathSpec.from_lines(
'gitwildmatch',
force_render_paths.splitlines())

# -------------------
# Create the jinja2 environment:
# -------------------
Expand Down Expand Up @@ -714,6 +759,11 @@ def on_page_markdown(self, markdown, page, config,
# page is an object with a number of properties (title, url, ...)
# see: https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/pages.py
self.variables["page"] = copy(page)
# Define whether we must force the rendering of this page,
# based on filename (relative to docs_dir directory)
filename = page.file.src_path
force_rendering = self.force_page_rendering(filename)

# set the markdown (for the first time)
self._markdown = markdown
# execute the pre-macro functions in the various modules
Expand All @@ -722,11 +772,12 @@ def on_page_markdown(self, markdown, page, config,
# render the macros
self.markdown = self.render(
markdown=self.markdown,
# page=page,
force_rendering=force_rendering
)
# Convert macros in the title from render (if exists)
# to answer 144
page.title = self.render(markdown=page.title)
page.title = self.render(markdown=page.title,
force_rendering=force_rendering)

# execute the post-macro functions in the various modules
for func in self.post_macro_functions:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# Initialization
# --------------------

VERSION_NUMBER = '1.0.7'
VERSION_NUMBER = '1.1.0'

# required if you want to run document/test
# pip install 'mkdocs-macros-plugin[test]'
Expand Down
10 changes: 8 additions & 2 deletions test/opt-in/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Hello World
# This is a test of opt-in with various mechanisms

**This is the case of opt-in, where `render_by_default` is set to
`false` in the config file.**


{# The jinja2 code in this index page should not be rendered
(since there must be an opt-in) #}

## Should not be rendered
{{ macros_info() }}

10 changes: 10 additions & 0 deletions test/opt-in/docs/noname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
render_macros: true # opt-in
title: Opt-in by page header
---
# {{ title }}

This page should be rendered, because the variable `render_macros`
is set to `true` in this page.

{{ macros_info() }}
15 changes: 15 additions & 0 deletions test/opt-in/docs/not_rendered/noname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
text: Hello world
times: 5
---

# This page must NOT be rendered

You should have some uninterpreted Jinja2 here.

This is because there is opt-in (`render_by_default` is set to `false`).


{% for n in range(times) %}
- {{ n }}: {{text}}
{% endfor %}
7 changes: 0 additions & 7 deletions test/opt-in/docs/page_with_macros.md

This file was deleted.

13 changes: 13 additions & 0 deletions test/opt-in/docs/render_this_one.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Opt-in by paths
---
# {{ title }}

This page must be rendered because there is `render_*.md` in the
`force_render_paths` variable, and the name of this page is
**`{{page.file.src_uri}}`**.

{{ macros_info() }}



17 changes: 17 additions & 0 deletions test/opt-in/docs/rendered/noname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
text: Hello world
times: 10
---

# Opt-in by directory

This page is rendered, because the `rendered/` path is
specified in the `mkdocs.yml` file,
and the original filename of this page is **`{{page.file.src_uri}}`**.


{% for n in range(times) %}
- {{ n }}: {{text}}
{% endfor %}


11 changes: 10 additions & 1 deletion test/opt-in/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ theme: mkdocs

nav:
- Home: index.md
- Second: page_with_macros.md # opt-in
- Other: not_rendered/noname.md
- Rendered (by header): noname.md # opt-in by header
- Rendered (by dir): rendered/noname.md # opt-in by directory
- Rendered (by name): render_this_one.md # opt-in by file pattern


plugins:
Expand All @@ -12,4 +15,10 @@ plugins:
# do not render the pages by default
# requires an opt-in
render_by_default: false
# render that path:
force_render_paths: |
# this directory will be rendered:
rendered/
render_*.md

0 comments on commit 827943d

Please sign in to comment.