Skip to content

Commit

Permalink
Test the hooks for external registration (#237)
Browse files Browse the repository at this point in the history
    - added the register_macros test case
    - reimplemented the .variables, .macros and .filters
      properties for MacrosDocProject
    - updated documentation, to document hooks scripts
      (including comparison with MkDocs-Macros modules)
  • Loading branch information
Laurent Franceschetti committed Oct 10, 2024
1 parent 16be58d commit 32cd528
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 48 deletions.
65 changes: 27 additions & 38 deletions mkdocs_macros/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
# The default name of the Python module:
DEFAULT_MODULE_NAME = 'main' # main.py

# the directory where the rendered macros must go
RENDERED_MACROS_DIRNAME = '__docs_macros_rendered'




Expand Down Expand Up @@ -295,20 +294,7 @@ def reverse(x):



@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



# ------------------------------------------------
Expand Down Expand Up @@ -378,6 +364,7 @@ def register_macros(self, items:dict):
Register macros (hook for other plugins).
These will be added last, and raise an exception if already present.
"""
trace(f"Registering external macros: {list(items)}")
try:
# after on_config
self._macros
Expand All @@ -393,6 +380,7 @@ def register_filters(self, items:dict):
Register filters (hook for other plugins).
These will be added last, and raise an exception if already present.
"""
trace(f"Registering external filters: {list(items)}")
try:
self._filters
register_items('filter', self.filters, items)
Expand All @@ -407,6 +395,7 @@ def register_variables(self, items:dict):
Register variables (hook for other plugins).
These will be added last, and raise an exception if already present.
"""
trace(f"Registering external variables: {list(items)}")
try:
# after on_config
self._variables
Expand Down Expand Up @@ -723,6 +712,7 @@ def on_config(self, config):
From the configuration file, builds a Jinj2 environment
with variables, functions and filters.
"""
trace("Configuring the macros environment...")
# WARNING: this is not the config argument:
trace("Macros arguments\n", self.config)
# define the variables and macros as dictionaries
Expand Down Expand Up @@ -770,18 +760,7 @@ def on_config(self, config):
register_items('macro' , self.macros , self._add_macros )
register_items('filter' , self.filters , self._add_filters )

# Provide information:
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("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))
Expand Down Expand Up @@ -856,16 +835,26 @@ 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)
trace("End of environment config")

def on_pre_build(self, *, config):
"""
Provide information on the variables.
It is put here, in case some plugin hooks into the config,
after the execution of the `on_config()` of this plugin.
"""
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("Config filters:", list(self.filters.keys()))
debug("Config filters:", payload=json.dumps(self.filters,
cls=CustomEncoder))


def on_nav(self, nav, config, files):
"""
Expand Down
2 changes: 1 addition & 1 deletion mkdocs_macros/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def default(self, obj: Any) -> Any:
try:
return super().default(obj)
except TypeError:
print(f"CANNOT INTERPRET {obj.__class__}")
debug(f"json: cannot encode {obj.__class__}")
return str(obj)


Expand Down
70 changes: 66 additions & 4 deletions test/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"""

import warnings
import json
import subprocess


from super_collections import SuperDict
from mkdocs_test import DocProject, MkDocsPage


Expand All @@ -25,18 +30,75 @@ def is_rendered(self):
class MacrosDocProject(DocProject):
"Specific for MkDocs-Macros"

def build(self, strict:bool=False) -> subprocess.CompletedProcess:
"""
Build the documentation, to perform the tests
Verbose is forced to True, to get the variables, functions and filters
"""
super().build(strict=strict, verbose=True)

@property
def pages(self) -> dict[MacrosPage]:
"List of pages"
pages = super().pages
return {key: MacrosPage(value) for key, value in pages.items()}

@property
def variables(self):
"Alias for config.extra"
return self.config.extra


@property
def macros_plugin(self):
"Information on the plugin"
return self.get_plugin('macros')

# ------------------------------------
# Get information through the payload
# ------------------------------------
@property
def variables(self):
"Return the variables"
try:
return self._variables
except AttributeError:
entry = self.find_entry("config variables",
source='macros',
severity='debug')
if entry and entry.payload:
self._variables = SuperDict(json.loads(entry.payload))
else:
print(entry)
raise ValueError("Cannot find variables")
return self._variables


@property
def macros(self):
"Return the macros"
try:
return self._macros
except AttributeError:
entry = self.find_entry("config macros",
source='macros',
severity='debug')
if entry and entry.payload:
self._macros = SuperDict(json.loads(entry.payload))
else:
print(entry)
raise ValueError("Cannot find macros")
return self._macros


@property
def filters(self):
"Return the filters"
try:
return self._filters
except AttributeError:
entry = self.find_entry("config filters",
source='macros',
severity='debug')
if entry and entry.payload:
self._filters = SuperDict(json.loads(entry.payload))
else:
print(entry)
raise ValueError("Cannot find filters")
return self._filters
Empty file.
20 changes: 20 additions & 0 deletions test/register_macros/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
message: Vive Zorglub
---

# Main Page

This project contains a registered macro

## Variables
I want to write 'foo': {{ foo }}

x2 is also present: {{ x2 }}

## Macros

Calculation: {{ bar(2, 5) }}

## Filters

I want to scramble '{{ message }}': {{ message | scramble }}
8 changes: 8 additions & 0 deletions test/register_macros/docs/second.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Second page

This page shows the information on variables,
to understand, how the functions, variables and filters
have been registered.


{{ macros_info() }}
44 changes: 44 additions & 0 deletions test/register_macros/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
def foo(x:int, y:str):
"First macro"
return f"{x} and {y}"

def bar(x:int, y:int):
"Second macro"
return x + y

def scramble(s:str, length:int=None):
"""
Dummy filter to reverse the string and swap the case of each character.
Usage in Markdown page:
{{ "Hello world" | scramble }} -> Dlrow Olleh
{{ "Hello world" | scramble(6) }} -> Dlrow
"""
# Split the phrase into words
words = s.split()
# Reverse each word and then reverse the order of the words
reversed_words = [word[::-1].capitalize() for word in words][::-1]
# Join the reversed words to form the new phrase
new_phrase = ' '.join(reversed_words)
if length:
new_phrase = new_phrase[length]
return new_phrase


MY_FUNCTIONS = {"foo": foo, "bar": bar}
MY_VARIABLES = {"x1": 5, "x2": 'hello world'}
MY_FILTERS = {"scramble": scramble}


def on_config(config, **kwargs):
"Add the functions variables and filters to the mix"
# get MkdocsMacros plugin, but only if present
macros_plugin = config.plugins.get("macros")
if macros_plugin:
macros_plugin.register_macros(MY_FUNCTIONS)
macros_plugin.register_variables(MY_VARIABLES)
macros_plugin.register_filters(MY_FILTERS)
else:
raise SystemError("Cannot find macros plugin!")

19 changes: 19 additions & 0 deletions test/register_macros/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
site_name: Testing the hooks
theme: readthedocs

nav:
- Home: index.md
- Next page: second.md

hooks:
# Mkdocs hook for testing the Mkdocs-Macros hook
- hooks.py

plugins:
- search
- macros
- test

extra:
greeting: Hello World!

70 changes: 70 additions & 0 deletions test/register_macros/test_doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
Testing the project
(C) Laurent Franceschetti 2024
"""


import pytest

from test.fixture import MacrosDocProject


from .hooks import MY_VARIABLES, MY_FUNCTIONS, MY_FILTERS, bar, scramble


def test_pages():
project = MacrosDocProject(".")
build_result = project.build(strict=True)
# did not fail
return_code = project.build_result.returncode
assert not return_code, f"Build returned with {return_code} {build_result.args})"

# check the presence of variables in the environment
print("Variables:", list(project.variables.keys()))
for variable in MY_VARIABLES:
assert variable in project.variables
print(f"{variable}: {project.variables[variable]}")

print("Macros:", list(project.macros.keys()))
for macro in MY_FUNCTIONS:
assert macro in project.macros
print(f"{macro}: {project.macros[macro]}")

print("Filters:", list(project.filters.keys()))
for filter in MY_FILTERS:
assert filter in project.filters
print(f"{filter}: {project.filters[filter]}")

# ----------------
# First page
# ----------------


page = project.get_page('index')
assert page.is_markdown_rendered()
# variable
value = MY_VARIABLES['x2']
print(f"Check if x2 ('{value}') is present")
assert page.find(value, header="Variables")
# macro
print("Check macro: bar")
assert page.find(bar(2, 5), header="Macros")
# filter
message = page.meta.message
result = scramble(message)
print(f"Check filter: scramble('{message}') --> '{result}'")
assert page.find(result, header="Filters")




# ----------------
# Second page
# ----------------
# there is intentionally an error (`foo` does not exist)
page = project.get_page('second')
assert 'foo' not in project.config.extra
assert page.is_markdown_rendered()
assert not page.has_error()

Loading

0 comments on commit 32cd528

Please sign in to comment.