mkdocs-table-reader-plugin
Advanced tools
| Metadata-Version: 2.1 | ||
| Name: mkdocs-table-reader-plugin | ||
| Version: 2.2.2 | ||
| Version: 3.0.0 | ||
| Summary: MkDocs plugin to directly insert tables from files into markdown. | ||
@@ -66,5 +66,6 @@ Home-page: https://github.com/timvink/mkdocs-table-reader-plugin | ||
| Where the path is relative to the location of your project's `mkdocs.yml` file (although you can [change that](https://timvink.github.io/mkdocs-table-reader-plugin/options) to be relative to your `docs/` directory). | ||
| Where the path is relative to the location of your project's `mkdocs.yml` file, _or_ your project's `docs/` directory, _or_ the location of your markdown source file (all 3 possible locations will be searched, in that order). | ||
| - There are [readers](https://timvink.github.io/mkdocs-table-reader-plugin/readers/) for `.csv`, `.fwf`, `.json`, `.xlsx`, `.yaml` and `.tsv` files. There is also the `read_raw()` reader that will allow you to insert tables (or other content) already in markdown format. | ||
| - There are [readers](https://timvink.github.io/mkdocs-table-reader-plugin/readers/) for `.csv`, `.fwf`, `.json`, `.xls`, `.xlsx`, `.yaml`, `.feather` and `.tsv` files. There is also the `read_raw()` reader that will allow you to insert tables (or other content) already in markdown format. | ||
| - `table-reader` is compatible with [`mkdocs-macros-plugin`](https://mkdocs-macros-plugin.readthedocs.io/en/latest/), which means you can [dynamically insert tables using jinja2 syntax](https://timvink.github.io/mkdocs-table-reader-plugin/howto/use_jinja2/). | ||
@@ -71,0 +72,0 @@ ## Documentation and how-to guides |
@@ -6,6 +6,7 @@ import re | ||
| def replace_unescaped_pipes(text: str) -> str: | ||
| """ | ||
| Replace unescaped pipes. | ||
| For regex explanation, see https://regex101.com/r/s8H588/1 | ||
@@ -28,3 +29,5 @@ | ||
| # See https://github.com/astanin/python-tabulate/issues/241 | ||
| df.columns = [replace_unescaped_pipes(c) if isinstance(c, str) else c for c in df.columns] | ||
| df.columns = [ | ||
| replace_unescaped_pipes(c) if isinstance(c, str) else c for c in df.columns | ||
| ] | ||
@@ -36,3 +39,5 @@ # Avoid deprecated applymap warning on pandas>=2.0 | ||
| else: | ||
| df = df.applymap(lambda s: replace_unescaped_pipes(s) if isinstance(s, str) else s) | ||
| df = df.applymap( | ||
| lambda s: replace_unescaped_pipes(s) if isinstance(s, str) else s | ||
| ) | ||
@@ -43,3 +48,3 @@ if "index" not in markdown_kwargs: | ||
| markdown_kwargs["tablefmt"] = "pipe" | ||
| return df.to_markdown(**markdown_kwargs) | ||
@@ -63,5 +68,5 @@ | ||
| fixed_lines = [] | ||
| for line in text.split('\n'): | ||
| for line in text.split("\n"): | ||
| fixed_lines.append(textwrap.indent(line, leading_spaces)) | ||
| text = "\n".join(fixed_lines) | ||
| return text |
@@ -1,6 +0,4 @@ | ||
| import os | ||
| import re | ||
| import logging | ||
| from mkdocs.plugins import BasePlugin | ||
| from mkdocs.plugins import BasePlugin, get_plugin_logger | ||
| from mkdocs.config import config_options | ||
@@ -13,12 +11,16 @@ from mkdocs.exceptions import ConfigurationError | ||
| logger = logging.getLogger("mkdocs.plugins") | ||
| logger = get_plugin_logger("table-reader") | ||
| class TableReaderPlugin(BasePlugin): | ||
| config_scheme = ( | ||
| ("base_path", config_options.Choice(['docs_dir','config_dir'], default="config_dir")), | ||
| ("data_path", config_options.Type(str, default=".")), | ||
| ("search_page_directory", config_options.Type(bool, default=True)), | ||
| ("allow_missing_files", config_options.Type(bool, default=False)), | ||
| ("select_readers", config_options.ListOfItems(config_options.Choice(list(READERS.keys())), default = list(READERS.keys()))), | ||
| ( | ||
| "select_readers", | ||
| config_options.ListOfItems( | ||
| config_options.Choice(list(READERS.keys())), | ||
| default=list(READERS.keys()), | ||
| ), | ||
| ), | ||
| ) | ||
@@ -36,10 +38,62 @@ | ||
| """ | ||
| if "search_page_directory" in self.config: | ||
| logger.warning( | ||
| "[table-reader]: The 'search_page_directory' configuration option is deprecated, it will always be searched. Please remove it from your mkdocs.yml." | ||
| ) | ||
| if "base_path" in self.config: | ||
| logger.warning( | ||
| "[table-reader]: The 'base_path' configuration option is deprecated. Both the config_dir and docs_dir will be searched. Please remove it from your mkdocs.yml." | ||
| ) | ||
| 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.") | ||
| 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): | ||
@@ -65,19 +119,9 @@ """ | ||
| """ | ||
| # 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 0: to extract any leading whitespace | ||
| # match group 1: to extract the arguments (positional and keywords) | ||
@@ -88,5 +132,4 @@ tag_pattern = re.compile( | ||
| matches = re.findall(tag_pattern, markdown) | ||
| for result in matches: | ||
| # Deal with indentation | ||
@@ -100,32 +143,4 @@ # So we can fix inserting tables. | ||
| # 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 = fix_indentation(leading_spaces, markdown_table) | ||
| markdown_table = function(*pd_args, **pd_kwargs) | ||
@@ -137,6 +152,5 @@ # Insert markdown table | ||
| # This is always why when allow_missing_files=True we replaced the input tag. | ||
| markdown_table = fix_indentation(leading_spaces, markdown_table) | ||
| markdown = tag_pattern.sub(markdown_table, markdown, count=1) | ||
| return markdown | ||
| import pandas as pd | ||
| import yaml | ||
| import os | ||
| from pathlib import Path | ||
| import logging | ||
| from mkdocs_table_reader_plugin.utils import kwargs_in_func, kwargs_not_in_func | ||
| 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) | ||
@@ -16,4 +72,4 @@ df = pd.read_csv(*args, **read_kwargs) | ||
| def read_table(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_table(*args, **kwargs) -> str: | ||
| read_kwargs = kwargs_in_func(kwargs, pd.read_table) | ||
@@ -26,3 +82,4 @@ df = pd.read_table(*args, **read_kwargs) | ||
| def read_fwf(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_fwf(*args, **kwargs) -> str: | ||
| read_kwargs = kwargs_in_func(kwargs, pd.read_fwf) | ||
@@ -34,3 +91,5 @@ df = pd.read_fwf(*args, **read_kwargs) | ||
| def read_json(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_json(*args, **kwargs) -> str: | ||
| read_kwargs = kwargs_in_func(kwargs, pd.read_json) | ||
@@ -43,3 +102,4 @@ df = pd.read_json(*args, **read_kwargs) | ||
| def read_excel(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_excel(*args, **kwargs) -> str: | ||
| read_kwargs = kwargs_in_func(kwargs, pd.read_excel) | ||
@@ -52,4 +112,4 @@ df = pd.read_excel(*args, **read_kwargs) | ||
| def read_yaml(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_yaml(*args, **kwargs) -> str: | ||
| json_kwargs = kwargs_in_func(kwargs, pd.json_normalize) | ||
@@ -62,3 +122,5 @@ with open(args[0], "r") as f: | ||
| def read_feather(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_feather(*args, **kwargs) -> str: | ||
| read_kwargs = kwargs_in_func(kwargs, pd.read_feather) | ||
@@ -70,3 +132,5 @@ df = pd.read_feather(*args, **read_kwargs) | ||
| def read_raw(*args, **kwargs): | ||
| @ParseArgs | ||
| def read_raw(*args, **kwargs) -> str: | ||
| """Read a file as-is. | ||
@@ -91,2 +155,1 @@ | ||
| } | ||
@@ -7,3 +7,3 @@ """ | ||
| A downside of literal_eval() is that is cannot parse | ||
| special characters like newlines (\r\t or \n). We need those kind of characters because pandas.read_csv() accepts | ||
| special characters like newlines (\r\t or \n). We need those kind of characters because pandas.read_csv() accepts | ||
| a parameter 'sep' that could contain all sorts of regex. | ||
@@ -40,4 +40,4 @@ | ||
| special characters like newlines (\r\t or \n). | ||
| We need this because pandas.read_csv() accepts | ||
| We need this because pandas.read_csv() accepts | ||
| a parameter 'sep' that could contain all sorts of regex. | ||
@@ -49,3 +49,3 @@ | ||
| Returns: | ||
| str: The parsed literal python structure | ||
| str: The parsed literal python structure | ||
| """ | ||
@@ -64,4 +64,4 @@ if "\n" in string or "\\" in string or "\r" in string: | ||
| Parses a string to detect both args and kwargs. | ||
| Adapted code from | ||
| Adapted code from | ||
| https://stackoverflow.com/questions/9305387/string-of-kwargs-to-kwargs | ||
@@ -71,3 +71,3 @@ | ||
| input_str (str): string with positional and keyword arguments | ||
| Returns: | ||
@@ -78,8 +78,8 @@ args[List], kwargs[Dict] | ||
| segments = [] | ||
| current_segment = '' | ||
| current_segment = "" | ||
| in_quotes = False | ||
| quote_char = '' | ||
| quote_char = "" | ||
| bracket_count = 0 | ||
| tuple_count = 0 | ||
| for char in input_str: | ||
@@ -91,18 +91,18 @@ if char in "\"'" and not in_quotes: | ||
| in_quotes = False | ||
| quote_char = '' | ||
| elif char == '[': | ||
| quote_char = "" | ||
| elif char == "[": | ||
| bracket_count += 1 | ||
| elif char == ']': | ||
| elif char == "]": | ||
| bracket_count -= 1 | ||
| elif char == '(': | ||
| elif char == "(": | ||
| tuple_count += 1 | ||
| elif char == ')': | ||
| elif char == ")": | ||
| tuple_count -= 1 | ||
| elif char == ',' and not in_quotes and bracket_count == 0 and tuple_count == 0: | ||
| elif char == "," and not in_quotes and bracket_count == 0 and tuple_count == 0: | ||
| segments.append(current_segment.strip()) | ||
| current_segment = '' | ||
| current_segment = "" | ||
| continue | ||
| current_segment += char | ||
| segments.append(current_segment.strip()) # Add the last segment | ||
@@ -109,0 +109,0 @@ # end code generated by copilot, validated by unit tests |
| import os | ||
| from inspect import signature | ||
| from inspect import signature | ||
| def get_keywords(func): | ||
| return [p.name for p in signature(func).parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD or p.kind == p.KEYWORD_ONLY] | ||
| return [ | ||
| p.name | ||
| for p in signature(func).parameters.values() | ||
| if p.kind == p.POSITIONAL_OR_KEYWORD or p.kind == p.KEYWORD_ONLY | ||
| ] | ||
| def kwargs_in_func(keywordargs, func): | ||
| return {k:v for k, v in keywordargs.items() if k in get_keywords(func)} | ||
| return {k: v for k, v in keywordargs.items() if k in get_keywords(func)} | ||
| def kwargs_not_in_func(keywordargs, func): | ||
| return {k:v for k, v in keywordargs.items() if k not in get_keywords(func)} | ||
| return {k: v for k, v in keywordargs.items() if k not in get_keywords(func)} | ||
@@ -29,2 +36,1 @@ | ||
| os.chdir(self.savedPath) | ||
+4
-3
| Metadata-Version: 2.1 | ||
| Name: mkdocs-table-reader-plugin | ||
| Version: 2.2.2 | ||
| Version: 3.0.0 | ||
| Summary: MkDocs plugin to directly insert tables from files into markdown. | ||
@@ -66,5 +66,6 @@ Home-page: https://github.com/timvink/mkdocs-table-reader-plugin | ||
| Where the path is relative to the location of your project's `mkdocs.yml` file (although you can [change that](https://timvink.github.io/mkdocs-table-reader-plugin/options) to be relative to your `docs/` directory). | ||
| Where the path is relative to the location of your project's `mkdocs.yml` file, _or_ your project's `docs/` directory, _or_ the location of your markdown source file (all 3 possible locations will be searched, in that order). | ||
| - There are [readers](https://timvink.github.io/mkdocs-table-reader-plugin/readers/) for `.csv`, `.fwf`, `.json`, `.xlsx`, `.yaml` and `.tsv` files. There is also the `read_raw()` reader that will allow you to insert tables (or other content) already in markdown format. | ||
| - There are [readers](https://timvink.github.io/mkdocs-table-reader-plugin/readers/) for `.csv`, `.fwf`, `.json`, `.xls`, `.xlsx`, `.yaml`, `.feather` and `.tsv` files. There is also the `read_raw()` reader that will allow you to insert tables (or other content) already in markdown format. | ||
| - `table-reader` is compatible with [`mkdocs-macros-plugin`](https://mkdocs-macros-plugin.readthedocs.io/en/latest/), which means you can [dynamically insert tables using jinja2 syntax](https://timvink.github.io/mkdocs-table-reader-plugin/howto/use_jinja2/). | ||
@@ -71,0 +72,0 @@ ## Documentation and how-to guides |
+3
-2
@@ -41,5 +41,6 @@ [](https://github.com/timvink/mkdocs-table-reader-plugin/actions) | ||
| Where the path is relative to the location of your project's `mkdocs.yml` file (although you can [change that](https://timvink.github.io/mkdocs-table-reader-plugin/options) to be relative to your `docs/` directory). | ||
| Where the path is relative to the location of your project's `mkdocs.yml` file, _or_ your project's `docs/` directory, _or_ the location of your markdown source file (all 3 possible locations will be searched, in that order). | ||
| - There are [readers](https://timvink.github.io/mkdocs-table-reader-plugin/readers/) for `.csv`, `.fwf`, `.json`, `.xlsx`, `.yaml` and `.tsv` files. There is also the `read_raw()` reader that will allow you to insert tables (or other content) already in markdown format. | ||
| - There are [readers](https://timvink.github.io/mkdocs-table-reader-plugin/readers/) for `.csv`, `.fwf`, `.json`, `.xls`, `.xlsx`, `.yaml`, `.feather` and `.tsv` files. There is also the `read_raw()` reader that will allow you to insert tables (or other content) already in markdown format. | ||
| - `table-reader` is compatible with [`mkdocs-macros-plugin`](https://mkdocs-macros-plugin.readthedocs.io/en/latest/), which means you can [dynamically insert tables using jinja2 syntax](https://timvink.github.io/mkdocs-table-reader-plugin/howto/use_jinja2/). | ||
@@ -46,0 +47,0 @@ ## Documentation and how-to guides |
+1
-1
@@ -8,3 +8,3 @@ from setuptools import setup, find_packages | ||
| name="mkdocs-table-reader-plugin", | ||
| version="2.2.2", | ||
| version="3.0.0", | ||
| description="MkDocs plugin to directly insert tables from files into markdown.", | ||
@@ -11,0 +11,0 @@ long_description=long_description, |
+13
-0
@@ -377,1 +377,14 @@ """ | ||
| assert re.search(r"4242", contents) | ||
| def test_macros_jinja2_syntax(tmp_path): | ||
| tmp_proj = setup_clean_mkdocs_folder( | ||
| "tests/fixtures/jinja/mkdocs.yml", tmp_path | ||
| ) | ||
| result = build_docs_setup(tmp_proj) | ||
| assert result.exit_code == 0, "'mkdocs build' command failed" | ||
| # Make sure the file.csv is inserted | ||
| page_with_tag = tmp_proj / "site/index.html" | ||
| contents = page_with_tag.read_text() | ||
| assert re.search(r"531456", contents) | ||
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
45461
8.36%823
12.13%