Skip to content

Commit

Permalink
Add a testing framework, as described in #244
Browse files Browse the repository at this point in the history
  • Loading branch information
Laurent Franceschetti committed Sep 26, 2024
1 parent fa90b96 commit 6265d90
Show file tree
Hide file tree
Showing 18 changed files with 1,495 additions and 36 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/greetings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ jobs:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Welcome to this project and thank you!'' first issue'
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'
issue-message: 'Welcome to this project and thank you!'
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''
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ MANIFEST
# MkDocs
site/

# Mkdocs-Macros
__*/

# Other files (generated by mkdocs-macros or others)
cache*

Expand Down
9 changes: 7 additions & 2 deletions mkdocs_macros/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,13 @@ def render_file(filename):
return env.render(s, force_rendering=True)

@env.macro
def context(obj=env.variables):
"*Default Mkdocs-Macro*: List the defined variables"
def context(obj:dict=None):
"""
*Default Mkdocs-Macro*: List an object
(by default the variables)
"""
if not obj:
obj = env.variables
try:
return [(var, type(value).__name__, format_value(value))
for var, value in list_items(obj)]
Expand Down
145 changes: 128 additions & 17 deletions mkdocs_macros/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
import importlib
import os
from copy import copy
import pathspec
import json
from datetime import datetime

import yaml
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 All @@ -25,7 +26,8 @@
from mkdocs_macros.context import define_env
from mkdocs_macros.util import (
install_package, parse_package, trace, debug,
update, SuperDict, import_local_module, format_chatter, LOG,
update, SuperDict, import_local_module, format_chatter, LOG, get_log_level,
setup_directory, CustomEncoder
)

# ------------------------------------------
Expand All @@ -38,9 +40,26 @@
# The default name of the Python module:
DEFAULT_MODULE_NAME = 'main' # main.py

# Possible behavior in case of ignored variables or macros (first is default)
# the directory where the rendered macros must go
RENDERED_MACROS_DIRNAME = '__docs_macros_rendered'



# ------------------------------------------
# Debug
# ------------------------------------------

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


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

Expand Down Expand Up @@ -271,6 +290,27 @@ def reverse(x):
self.filters[name] = v
return v



@property
def rendered_macros_dir(self):
"""
The directory, beside the docs_dir, that contains
the rendered pages from the macros.
"""
try:
r = self._rendered_macros_dir
except AttributeError:
raise AttributeError("Rendered macros directory is undefined")
if not os.path.isdir(self._rendered_macros_dir):
raise FileNotFoundError("Rendered macros directory is defined "
"but does not exists")
return r


# ------------------------------------------------
# Property of the current page for on_page_markdown()
# ------------------------------------------------
@property
def page(self) -> Page:
"""
Expand All @@ -296,7 +336,10 @@ def markdown(self) -> str:
@markdown.setter
def markdown(self, value):
"""
Used to set the raw markdown of the current page
Used to set the raw markdown of the current page.
[Especially used in the `on_pre_page_macros()` and
`on_ost_page_macros()` hooks.]
"""
if not isinstance(value, str):
raise ValueError("Value provided to attribute markdown "
Expand Down Expand Up @@ -561,7 +604,12 @@ def _load_modules(self):
"module in '%s'." %
(local_module_name, self.project_dir))

def render(self, markdown: str, force_rendering:bool=False):

# ----------------------------------
# output elements
# ----------------------------------

def render(self, markdown: str, force_rendering:bool=False) -> str:
"""
Render a page through jinja2: it reads the code and
executes the macros.
Expand Down Expand Up @@ -605,11 +653,14 @@ def render(self, markdown: str, force_rendering:bool=False):
# this is a premature rendering, no meta variables in the page
meta_variables = {}


# Warning this is ternary logic(True, False, None: nothing said)
render_macros = None

if meta_variables:
# file_path = self.variables.page.file.src_path
file_path = self.page.file.src_path
debug(f"Metadata in page '{file_path}'",
payload=meta_variables)
# determine whether the page will be rendered or not
# the two formulations are accepted
render_macros = meta_variables.get('render_macros')
Expand Down Expand Up @@ -658,6 +709,44 @@ def render(self, markdown: str, force_rendering:bool=False):
else:
return error_message

def _save_debug_file(self, page:Page,
rendered_markdown:str) -> str:
"""
Saves a page to disk for debug/testing purposes,
with a reconstituted YAML front matter.
Argument:
- page: the Page (page.markdown contains the old markdown)
- rendered_mardkown (the new markdown)
Returns the saved document.
"""
dest_file = os.path.join(self.rendered_macros_dir,
page.file.src_path)
debug(f"Saving page '{page.title}' in destination file:",
dest_file)
# Create the subdirectory hierarchy if necessary
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
if page.meta:
# recreate the YAML header:
yaml_values = yaml.dump(dict(page.meta),
default_flow_style=False, sort_keys=False)
document = '\n'.join([ '---',
YAML_HEADER_WARNING,
yaml_values.strip(),
'---',
rendered_markdown
])
else:
# re-generate the document with YAML header
document = rendered_markdown
# write on file:
debug("Saved ")
with open(dest_file, 'w') as f:
f.write(document)
return document


# ----------------------------------
# Standard Hooks for a mkdocs plugin
# ----------------------------------
Expand All @@ -669,7 +758,7 @@ def on_config(self, config):
with variables, functions and filters.
"""
# WARNING: this is not the config argument:
trace("Macros arguments:", self.config)
trace("Macros arguments\n", self.config)
# define the variables and macros as dictionaries
# (for update function to work):
self._variables = SuperDict()
Expand Down Expand Up @@ -716,12 +805,20 @@ def on_config(self, config):
register_items('filter' , self.filters , self._add_filters )

# Provide information:
debug("Variables:", list(self.variables.keys()))
if len(extra):
trace("Extra variables (config file):", list(extra.keys()))
debug("Content of extra variables (config file):", extra)
trace("Config variables:", list(self.variables.keys()))
debug("Config variables:\n", payload=json.dumps(self.variables,
cls=CustomEncoder))
if self.macros:
trace("Config macros:", list(self.macros.keys()))
debug("Config macros:", payload=json.dumps(self.macros,
cls=CustomEncoder))
if self.filters:
trace("Extra filters (module):", list(self.filters.keys()))
trace("Config filters:", list(self.filters.keys()))
debug("Config filters:", payload=json.dumps(self.filters,
cls=CustomEncoder))
# if len(extra):
# trace("Extra variables (config file):", list(extra.keys()))
# debug("Content of extra variables (config file):\n", dict(extra))


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

# -------------------
# Setup the markdown (rendered) directory
# -------------------
docs_dir = config['docs_dir']
abs_docs_dir = os.path.abspath(docs_dir)
# recreate only if debug (otherewise delete):
recreate = get_log_level('DEBUG')
self._rendered_macros_dir = setup_directory(abs_docs_dir,
RENDERED_MACROS_DIRNAME,
recreate=recreate)

def on_nav(self, nav, config, files):
"""
Called after the site navigation is created.
Expand Down Expand Up @@ -840,14 +948,11 @@ def on_page_markdown(self, markdown, page:Page,
It uses the jinja2 directives, together with
variables, macros and filters, to create pure markdown code.
"""
# the site_navigation argument has been made optional
# (deleted in post-1.0 mkdocs, but maintained here
# for backward compatibility)
# We REALLY want the same object
self._page = page
if not self.variables:
return markdown
else:
trace("Rendering source page:", page.file.src_path)
# Update the page info in the document
# page is an object with a number of properties (title, url, ...)
# see: https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/pages.py
Expand Down Expand Up @@ -880,6 +985,12 @@ def on_page_markdown(self, markdown, page:Page,
# execute the post-macro functions in the various modules
for func in self.post_macro_functions:
func(self)

# save the rendered page, with its YAML header
if get_log_level('DEBUG'):
self._save_debug_file(page,
rendered_markdown=self.markdown)

return self.markdown

def on_post_build(self, config: config_options.Config):
Expand Down
Loading

0 comments on commit 6265d90

Please sign in to comment.