Skip to content

Commit

Permalink
Refactor for compatibility with macros
Browse files Browse the repository at this point in the history
  • Loading branch information
timvink authored Aug 15, 2024
1 parent 74817da commit 2cdc786
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 54 deletions.
81 changes: 40 additions & 41 deletions mkdocs_table_reader_plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,47 @@ def on_config(self, config, **kwargs):
Returns:
Config
"""
self.readers = {reader: READERS[reader].set_config_context(mkdocs_config=config, plugin_config=self.config) for reader in self.config.get('select_readers') if reader in self.config.get('select_readers',[])}

plugins = [p for p in config.get("plugins")]

for post_load_plugin in ["macros", "markdownextradata"]:
# Plugins required before table-reader
for post_load_plugin in ["markdownextradata"]:
if post_load_plugin in plugins:
if plugins.index("table-reader") > plugins.index(post_load_plugin):
raise ConfigurationError(f"[table-reader]: Incompatible plugin order: Define 'table-reader' before '{post_load_plugin}' in your mkdocs.yml.")

# Plugins required after table-reader
for post_load_plugin in ["macros"]:
if post_load_plugin in plugins:
if plugins.index("table-reader") < plugins.index(post_load_plugin):
raise ConfigurationError(f"[table-reader]: Incompatible plugin order: Define 'table-reader' after '{post_load_plugin}' in your mkdocs.yml.")

if "macros" in config.plugins:
config.plugins['macros'].macros.update(self.readers)
config.plugins['macros'].variables['macros'].update(self.readers)
config.plugins['macros'].env.globals.update(self.readers)
self.external_jinja_engine = True
else:
self.external_jinja_engine = False


def on_pre_page(self, page, config, **kwargs):
"""
See https://www.mkdocs.org/dev-guide/plugins/#on_pre_page.
Args:
page: mkdocs.nav.Page instance
config: global configuration object
Returns:
Page
"""
# store the current page in the plugin config
# because the readers have access to the plugin config, they can know where the current page is
# this way, they can check that directory too
self.config._current_page = page.file.abs_src_path
return page

def on_page_markdown(self, markdown, page, config, files, **kwargs):
"""
Expand All @@ -60,21 +94,11 @@ def on_page_markdown(self, markdown, page, config, files, **kwargs):
Returns:
str: Markdown source text of page as string
"""
# Determine the mkdocs directory
# We do this during the on_page_markdown() event because other plugins
# might have changed the directory.
if self.config.get("base_path") == "config_dir":
mkdocs_dir = os.path.dirname(os.path.abspath(config["config_file_path"]))
if self.config.get("base_path") == "docs_dir":
mkdocs_dir = os.path.abspath(config["docs_dir"])

# Define directories to search for tables
search_directories = [os.path.join(mkdocs_dir, self.config.get("data_path"))]
if self.config.get("search_page_directory"):
search_directories.append(os.path.dirname(page.file.abs_src_path))
if self.external_jinja_engine:
return markdown

for reader in self.config.get('select_readers'):
function = READERS[reader]
for reader in self.readers:
function = self.readers[reader]
# Regex pattern for tags like {{ read_csv(..) }}
# match group 0: to extract any leading whitespace
# match group 1: to extract the arguments (positional and keywords)
Expand All @@ -92,36 +116,11 @@ def on_page_markdown(self, markdown, page, config, files, **kwargs):

# Safely parse the arguments
pd_args, pd_kwargs = parse_argkwarg(result[1])

# Extract the filepath,
# which is the first positional argument
# or a named argument when there are no positional arguments
if len(pd_args) > 0:
input_file_path = pd_args.pop(0)
else:
input_file_path = pd_kwargs.pop("filepath_or_buffer")

# Validate if file exists
search_file_paths = [os.path.join(search_dir, input_file_path) for search_dir in search_directories]
valid_file_paths = [p for p in search_file_paths if os.path.exists(p)]
if len(valid_file_paths) == 0:
msg = f"[table-reader-plugin]: Cannot find table file '{input_file_path}'. The following directories were searched: {*search_directories,}"
if self.config.get("allow_missing_files"):
logger.warning(msg)

# Add message in markdown
updated_tag = fix_indentation(leading_spaces, f"{{{{ Cannot find '{input_file_path}' }}}}")

markdown = tag_pattern.sub(updated_tag, markdown, count=1)

continue
else:
raise FileNotFoundError(msg)

# Load the table
# note we use the first valid file paths,
# where we first search the 'data_path' and then the page's directory.
markdown_table = function(valid_file_paths[0], *pd_args, **pd_kwargs)
markdown_table = function(*pd_args, **pd_kwargs)
markdown_table = fix_indentation(leading_spaces, markdown_table)

# Insert markdown table
Expand Down
79 changes: 70 additions & 9 deletions mkdocs_table_reader_plugin/readers.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,71 @@
import pandas as pd
import yaml
import os
from pathlib import Path
import logging

import functools

from mkdocs_table_reader_plugin.utils import kwargs_in_func, kwargs_not_in_func
from mkdocs_table_reader_plugin.markdown import convert_to_md_table

def read_csv(*args, **kwargs):

logger = logging.getLogger("mkdocs.plugins")


class ParseArgs:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.mkdocs_config = None
self.plugin_config = None

def set_config_context(self, mkdocs_config, plugin_config):
self.mkdocs_config = mkdocs_config
self.plugin_config = plugin_config
return self

def __call__(self, *args, **kwargs):
assert self.mkdocs_config is not None, "mkdocs_config is not set"
assert self.plugin_config is not None, "plugin_config is not set"

# Extract the filepath,
# which is the first positional argument
# or a named argument when there are no positional arguments
args = list(args)
if len(args) > 0:
input_file_name = args.pop(0)
else:
input_file_name = kwargs.pop("filepath_or_buffer")

possible_file_paths = [
Path(os.path.dirname(os.path.abspath(self.mkdocs_config["config_file_path"]))) / Path(self.plugin_config.get("data_path")) / input_file_name,
Path(os.path.abspath(self.mkdocs_config["docs_dir"])) / Path(self.plugin_config.get("data_path")) / input_file_name,
Path(self.plugin_config._current_page).parent / input_file_name
]
valid_file_paths = [path for path in possible_file_paths if path.exists()]
if len(valid_file_paths) == 0:
msg = f"[table-reader-plugin]: Cannot find table file '{input_file_name}'. The following directories were searched: {*possible_file_paths,}"
if self.plugin_config.get("allow_missing_files"):
logger.warning(msg)
return f"{{{{ Cannot find '{input_file_name}' }}}}"
else:
raise FileNotFoundError(msg)

return self.func(valid_file_paths[0], *args, **kwargs)



@ParseArgs
def read_csv(*args, **kwargs) -> str:
read_kwargs = kwargs_in_func(kwargs, pd.read_csv)
df = pd.read_csv(*args, **read_kwargs)

markdown_kwargs = kwargs_not_in_func(kwargs, pd.read_csv)
return convert_to_md_table(df, markdown_kwargs)


def read_table(*args, **kwargs):
@ParseArgs
def read_table(*args, **kwargs) -> str:

read_kwargs = kwargs_in_func(kwargs, pd.read_table)
df = pd.read_table(*args, **read_kwargs)
Expand All @@ -22,30 +74,35 @@ def read_table(*args, **kwargs):
return convert_to_md_table(df, markdown_kwargs)


def read_fwf(*args, **kwargs):
@ParseArgs
def read_fwf(*args, **kwargs) -> str:
read_kwargs = kwargs_in_func(kwargs, pd.read_fwf)
df = pd.read_fwf(*args, **read_kwargs)

markdown_kwargs = kwargs_not_in_func(kwargs, pd.read_fwf)
return convert_to_md_table(df, markdown_kwargs)

def read_json(*args, **kwargs):

@ParseArgs
def read_json(*args, **kwargs) -> str:
read_kwargs = kwargs_in_func(kwargs, pd.read_json)
df = pd.read_json(*args, **read_kwargs)

markdown_kwargs = kwargs_not_in_func(kwargs, pd.read_json)
return convert_to_md_table(df, markdown_kwargs)


def read_excel(*args, **kwargs):
@ParseArgs
def read_excel(*args, **kwargs) -> str:
read_kwargs = kwargs_in_func(kwargs, pd.read_excel)
df = pd.read_excel(*args, **read_kwargs)

markdown_kwargs = kwargs_not_in_func(kwargs, pd.read_excel)
return convert_to_md_table(df, markdown_kwargs)


def read_yaml(*args, **kwargs):
@ParseArgs
def read_yaml(*args, **kwargs) -> str:

json_kwargs = kwargs_in_func(kwargs, pd.json_normalize)
with open(args[0], "r") as f:
Expand All @@ -54,14 +111,18 @@ def read_yaml(*args, **kwargs):
markdown_kwargs = kwargs_not_in_func(kwargs, pd.json_normalize)
return convert_to_md_table(df, markdown_kwargs)

def read_feather(*args, **kwargs):

@ParseArgs
def read_feather(*args, **kwargs) -> str:
read_kwargs = kwargs_in_func(kwargs, pd.read_feather)
df = pd.read_feather(*args, **read_kwargs)

markdown_kwargs = kwargs_not_in_func(kwargs, pd.read_feather)
return convert_to_md_table(df, markdown_kwargs)

def read_raw(*args, **kwargs):

@ParseArgs
def read_raw(*args, **kwargs) -> str:
"""Read a file as-is.
Returns:
Expand Down
4 changes: 2 additions & 2 deletions tests/fixtures/basic_setup/mkdocs_w_macros.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ use_directory_urls: true

plugins:
- search
- table-reader
- macros
- macros
- table-reader
4 changes: 2 additions & 2 deletions tests/fixtures/basic_setup/mkdocs_w_macros_wrong_order.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ use_directory_urls: true

plugins:
- search
- macros
- table-reader
- table-reader
- macros
5 changes: 5 additions & 0 deletions tests/fixtures/jinja/docs/basic_table.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"a","b"
40,73
50,52
531456,80
"name","table1"
File renamed without changes.
15 changes: 15 additions & 0 deletions tests/fixtures/jinja/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Test page

This is a table that we load from the docs folder, because we set `data_path` to `docs`:

## Dynamically insert a list of tables using jinja2


{% set table_names = ["basic_table.csv","basic_table2.csv"] %}
{% for table_name in table_names %}

### table `{{ table_name }}`

{{ read_csv(table_name) }}

{% endfor %}
5 changes: 5 additions & 0 deletions tests/fixtures/jinja/docs/subfolder/sub_table.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"a","b"
40,73
50,52
531456,80
"name","table1"
5 changes: 5 additions & 0 deletions tests/fixtures/jinja/docs/subfolder/subpage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# page

Test that searching in page directory works

{{ read_csv("sub_table.csv") }}
7 changes: 7 additions & 0 deletions tests/fixtures/jinja/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
site_name: test git_table_reader site
use_directory_urls: true

plugins:
- search
- macros
- table-reader
1 change: 1 addition & 0 deletions tests/test_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pyarrow
# linting
pyflakes
# documentation
mkdocs-macros-plugin
mkdocs-material
mkdocs-markdownextradata-plugin
mkdocs-macros-plugin
Expand Down

0 comments on commit 2cdc786

Please sign in to comment.