Skip to content

Commit 6265d90

Browse files
author
Laurent Franceschetti
committed
Add a testing framework, as described in #244
1 parent fa90b96 commit 6265d90

File tree

18 files changed

+1495
-36
lines changed

18 files changed

+1495
-36
lines changed

.github/workflows/greetings.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ jobs:
99
- uses: actions/first-interaction@v1
1010
with:
1111
repo-token: ${{ secrets.GITHUB_TOKEN }}
12-
issue-message: 'Welcome to this project and thank you!'' first issue'
13-
pr-message: 'Thank you for submitting a PR, this is appreciated. Please do not forget to submit a corresponding issue, and to reference its number in the PR'' first pr'
12+
issue-message: 'Welcome to this project and thank you!'
13+
pr-message: 'Thank you for submitting a PR, this is appreciated. Please do not forget to submit a corresponding issue, and to reference its number in the PR''

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ MANIFEST
3030
# MkDocs
3131
site/
3232

33+
# Mkdocs-Macros
34+
__*/
35+
3336
# Other files (generated by mkdocs-macros or others)
3437
cache*
3538

mkdocs_macros/context.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,13 @@ def render_file(filename):
322322
return env.render(s, force_rendering=True)
323323

324324
@env.macro
325-
def context(obj=env.variables):
326-
"*Default Mkdocs-Macro*: List the defined variables"
325+
def context(obj:dict=None):
326+
"""
327+
*Default Mkdocs-Macro*: List an object
328+
(by default the variables)
329+
"""
330+
if not obj:
331+
obj = env.variables
327332
try:
328333
return [(var, type(value).__name__, format_value(value))
329334
for var, value in list_items(obj)]

mkdocs_macros/plugin.py

Lines changed: 128 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
import importlib
1010
import os
1111
from copy import copy
12+
import pathspec
13+
import json
14+
from datetime import datetime
1215

1316
import yaml
1417
from jinja2 import (
1518
Environment, FileSystemLoader, Undefined, DebugUndefined, StrictUndefined,
1619
)
17-
import pathspec
18-
1920
from mkdocs.config import config_options
2021
from mkdocs.config.config_options import Type as PluginType
2122
from mkdocs.plugins import BasePlugin
@@ -25,7 +26,8 @@
2526
from mkdocs_macros.context import define_env
2627
from mkdocs_macros.util import (
2728
install_package, parse_package, trace, debug,
28-
update, SuperDict, import_local_module, format_chatter, LOG,
29+
update, SuperDict, import_local_module, format_chatter, LOG, get_log_level,
30+
setup_directory, CustomEncoder
2931
)
3032

3133
# ------------------------------------------
@@ -38,9 +40,26 @@
3840
# The default name of the Python module:
3941
DEFAULT_MODULE_NAME = 'main' # main.py
4042

41-
# Possible behavior in case of ignored variables or macros (first is default)
43+
# the directory where the rendered macros must go
44+
RENDERED_MACROS_DIRNAME = '__docs_macros_rendered'
45+
46+
47+
48+
# ------------------------------------------
49+
# Debug
50+
# ------------------------------------------
4251

52+
# message for the front matter of markdown pages saved after rendering:
53+
YAML_HEADER_WARNING = (
54+
"# IMPORTANT NOTE:"
55+
"\n# This page was automatically generated by MkDocs-Macros "
56+
"for debug purposes,"
57+
"\n# after rendering the macros as plain text."
58+
f"\n# ({datetime.now():%Y-%m-%d %H:%M:%S})"
59+
)
4360

61+
62+
# Possible behavior in case of ignored variables or macros (first is default)
4463
class LaxUndefined(Undefined):
4564
"Pass anything wrong as blank"
4665

@@ -271,6 +290,27 @@ def reverse(x):
271290
self.filters[name] = v
272291
return v
273292

293+
294+
295+
@property
296+
def rendered_macros_dir(self):
297+
"""
298+
The directory, beside the docs_dir, that contains
299+
the rendered pages from the macros.
300+
"""
301+
try:
302+
r = self._rendered_macros_dir
303+
except AttributeError:
304+
raise AttributeError("Rendered macros directory is undefined")
305+
if not os.path.isdir(self._rendered_macros_dir):
306+
raise FileNotFoundError("Rendered macros directory is defined "
307+
"but does not exists")
308+
return r
309+
310+
311+
# ------------------------------------------------
312+
# Property of the current page for on_page_markdown()
313+
# ------------------------------------------------
274314
@property
275315
def page(self) -> Page:
276316
"""
@@ -296,7 +336,10 @@ def markdown(self) -> str:
296336
@markdown.setter
297337
def markdown(self, value):
298338
"""
299-
Used to set the raw markdown of the current page
339+
Used to set the raw markdown of the current page.
340+
341+
[Especially used in the `on_pre_page_macros()` and
342+
`on_ost_page_macros()` hooks.]
300343
"""
301344
if not isinstance(value, str):
302345
raise ValueError("Value provided to attribute markdown "
@@ -561,7 +604,12 @@ def _load_modules(self):
561604
"module in '%s'." %
562605
(local_module_name, self.project_dir))
563606

564-
def render(self, markdown: str, force_rendering:bool=False):
607+
608+
# ----------------------------------
609+
# output elements
610+
# ----------------------------------
611+
612+
def render(self, markdown: str, force_rendering:bool=False) -> str:
565613
"""
566614
Render a page through jinja2: it reads the code and
567615
executes the macros.
@@ -605,11 +653,14 @@ def render(self, markdown: str, force_rendering:bool=False):
605653
# this is a premature rendering, no meta variables in the page
606654
meta_variables = {}
607655

608-
609656
# Warning this is ternary logic(True, False, None: nothing said)
610657
render_macros = None
611658

612659
if meta_variables:
660+
# file_path = self.variables.page.file.src_path
661+
file_path = self.page.file.src_path
662+
debug(f"Metadata in page '{file_path}'",
663+
payload=meta_variables)
613664
# determine whether the page will be rendered or not
614665
# the two formulations are accepted
615666
render_macros = meta_variables.get('render_macros')
@@ -658,6 +709,44 @@ def render(self, markdown: str, force_rendering:bool=False):
658709
else:
659710
return error_message
660711

712+
def _save_debug_file(self, page:Page,
713+
rendered_markdown:str) -> str:
714+
"""
715+
Saves a page to disk for debug/testing purposes,
716+
with a reconstituted YAML front matter.
717+
718+
Argument:
719+
- page: the Page (page.markdown contains the old markdown)
720+
- rendered_mardkown (the new markdown)
721+
722+
Returns the saved document.
723+
"""
724+
dest_file = os.path.join(self.rendered_macros_dir,
725+
page.file.src_path)
726+
debug(f"Saving page '{page.title}' in destination file:",
727+
dest_file)
728+
# Create the subdirectory hierarchy if necessary
729+
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
730+
if page.meta:
731+
# recreate the YAML header:
732+
yaml_values = yaml.dump(dict(page.meta),
733+
default_flow_style=False, sort_keys=False)
734+
document = '\n'.join([ '---',
735+
YAML_HEADER_WARNING,
736+
yaml_values.strip(),
737+
'---',
738+
rendered_markdown
739+
])
740+
else:
741+
# re-generate the document with YAML header
742+
document = rendered_markdown
743+
# write on file:
744+
debug("Saved ")
745+
with open(dest_file, 'w') as f:
746+
f.write(document)
747+
return document
748+
749+
661750
# ----------------------------------
662751
# Standard Hooks for a mkdocs plugin
663752
# ----------------------------------
@@ -669,7 +758,7 @@ def on_config(self, config):
669758
with variables, functions and filters.
670759
"""
671760
# WARNING: this is not the config argument:
672-
trace("Macros arguments:", self.config)
761+
trace("Macros arguments\n", self.config)
673762
# define the variables and macros as dictionaries
674763
# (for update function to work):
675764
self._variables = SuperDict()
@@ -716,12 +805,20 @@ def on_config(self, config):
716805
register_items('filter' , self.filters , self._add_filters )
717806

718807
# Provide information:
719-
debug("Variables:", list(self.variables.keys()))
720-
if len(extra):
721-
trace("Extra variables (config file):", list(extra.keys()))
722-
debug("Content of extra variables (config file):", extra)
808+
trace("Config variables:", list(self.variables.keys()))
809+
debug("Config variables:\n", payload=json.dumps(self.variables,
810+
cls=CustomEncoder))
811+
if self.macros:
812+
trace("Config macros:", list(self.macros.keys()))
813+
debug("Config macros:", payload=json.dumps(self.macros,
814+
cls=CustomEncoder))
723815
if self.filters:
724-
trace("Extra filters (module):", list(self.filters.keys()))
816+
trace("Config filters:", list(self.filters.keys()))
817+
debug("Config filters:", payload=json.dumps(self.filters,
818+
cls=CustomEncoder))
819+
# if len(extra):
820+
# trace("Extra variables (config file):", list(extra.keys()))
821+
# debug("Content of extra variables (config file):\n", dict(extra))
725822

726823

727824
# Define the spec for the file paths whose rendering must be forced.
@@ -793,6 +890,17 @@ def on_config(self, config):
793890
# update environment with the custom filters:
794891
self.env.filters.update(self.filters)
795892

893+
# -------------------
894+
# Setup the markdown (rendered) directory
895+
# -------------------
896+
docs_dir = config['docs_dir']
897+
abs_docs_dir = os.path.abspath(docs_dir)
898+
# recreate only if debug (otherewise delete):
899+
recreate = get_log_level('DEBUG')
900+
self._rendered_macros_dir = setup_directory(abs_docs_dir,
901+
RENDERED_MACROS_DIRNAME,
902+
recreate=recreate)
903+
796904
def on_nav(self, nav, config, files):
797905
"""
798906
Called after the site navigation is created.
@@ -840,14 +948,11 @@ def on_page_markdown(self, markdown, page:Page,
840948
It uses the jinja2 directives, together with
841949
variables, macros and filters, to create pure markdown code.
842950
"""
843-
# the site_navigation argument has been made optional
844-
# (deleted in post-1.0 mkdocs, but maintained here
845-
# for backward compatibility)
846-
# We REALLY want the same object
847951
self._page = page
848952
if not self.variables:
849953
return markdown
850954
else:
955+
trace("Rendering source page:", page.file.src_path)
851956
# Update the page info in the document
852957
# page is an object with a number of properties (title, url, ...)
853958
# see: https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/pages.py
@@ -880,6 +985,12 @@ def on_page_markdown(self, markdown, page:Page,
880985
# execute the post-macro functions in the various modules
881986
for func in self.post_macro_functions:
882987
func(self)
988+
989+
# save the rendered page, with its YAML header
990+
if get_log_level('DEBUG'):
991+
self._save_debug_file(page,
992+
rendered_markdown=self.markdown)
993+
883994
return self.markdown
884995

885996
def on_post_build(self, config: config_options.Config):

0 commit comments

Comments
 (0)