From 21bc7ca550a341743777db53c81086862a3dcfb5 Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 10:20:41 +0000 Subject: [PATCH 1/8] better error for monorepo --- tests/test_builds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_builds.py b/tests/test_builds.py index ab10cc6..ab6960f 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -723,4 +723,4 @@ def test_monorepo_compat(tmp_path): # repo.git.add(".") # repo.git.commit(message="add all", author=author, date="1500854705") - assert result.exit_code == 0, f"'mkdocs build' command failed with:\n\n{result.stdout}" + assert result.exit_code == 0, f"'mkdocs build' command failed with:\n\n{result.stdout}\n\n{result.stderr}" From 07f0cb79335c4e3c6fe09c91dedf3f7bf69061a6 Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 10:29:32 +0000 Subject: [PATCH 2/8] apply ruff format --- .../dates.py | 70 ++--- .../plugin.py | 22 +- tests/fixtures/mkdocs-gen-files/gen_pages.py | 1 - .../fixtures/with_mknotebooks/docs/demo.ipynb | 91 ++++--- tests/test_builds.py | 257 ++++++++---------- tests/test_dates.py | 32 +-- tests/test_exclude.py | 4 +- tests/test_parse_git_ignore_revs.py | 3 +- 8 files changed, 223 insertions(+), 257 deletions(-) diff --git a/src/mkdocs_git_revision_date_localized_plugin/dates.py b/src/mkdocs_git_revision_date_localized_plugin/dates.py index 3fd4dd6..c66e4a5 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/dates.py +++ b/src/mkdocs_git_revision_date_localized_plugin/dates.py @@ -43,49 +43,49 @@ def get_date_formats( def strftime_to_babel_format(fmt: str) -> str: """ Convert strftime format string to Babel format pattern. - + Args: fmt (str): strftime format string - + Returns: str: Babel format pattern """ # Dictionary mapping strftime directives to Babel format patterns mapping = { - '%a': 'EEE', # Weekday abbreviated - '%A': 'EEEE', # Weekday full - '%b': 'MMM', # Month abbreviated - '%B': 'MMMM', # Month full - '%c': '', # Locale's date and time (not directly mappable) - '%d': 'dd', # Day of month zero-padded - '%-d': 'd', # Day of month - '%e': 'd', # Day of month space-padded - '%f': 'SSSSSS', # Microsecond - '%H': 'HH', # Hour 24h zero-padded - '%-H': 'H', # Hour 24h - '%I': 'hh', # Hour 12h zero-padded - '%-I': 'h', # Hour 12h - '%j': 'DDD', # Day of year - '%m': 'MM', # Month zero-padded - '%-m': 'M', # Month - '%M': 'mm', # Minute zero-padded - '%-M': 'm', # Minute - '%p': 'a', # AM/PM - '%S': 'ss', # Second zero-padded - '%-S': 's', # Second - '%w': 'e', # Weekday as number - '%W': 'w', # Week of year - '%x': '', # Locale's date (not directly mappable) - '%X': '', # Locale's time (not directly mappable) - '%y': 'yy', # Year without century - '%Y': 'yyyy', # Year with century - '%z': 'Z', # UTC offset - '%Z': 'z', # Timezone name - '%%': '%' # Literal % + "%a": "EEE", # Weekday abbreviated + "%A": "EEEE", # Weekday full + "%b": "MMM", # Month abbreviated + "%B": "MMMM", # Month full + "%c": "", # Locale's date and time (not directly mappable) + "%d": "dd", # Day of month zero-padded + "%-d": "d", # Day of month + "%e": "d", # Day of month space-padded + "%f": "SSSSSS", # Microsecond + "%H": "HH", # Hour 24h zero-padded + "%-H": "H", # Hour 24h + "%I": "hh", # Hour 12h zero-padded + "%-I": "h", # Hour 12h + "%j": "DDD", # Day of year + "%m": "MM", # Month zero-padded + "%-m": "M", # Month + "%M": "mm", # Minute zero-padded + "%-M": "m", # Minute + "%p": "a", # AM/PM + "%S": "ss", # Second zero-padded + "%-S": "s", # Second + "%w": "e", # Weekday as number + "%W": "w", # Week of year + "%x": "", # Locale's date (not directly mappable) + "%X": "", # Locale's time (not directly mappable) + "%y": "yy", # Year without century + "%Y": "yyyy", # Year with century + "%z": "Z", # UTC offset + "%Z": "z", # Timezone name + "%%": "%", # Literal % } - + result = fmt for strftime_code, babel_code in mapping.items(): result = result.replace(strftime_code, babel_code) - - return result \ No newline at end of file + + return result diff --git a/src/mkdocs_git_revision_date_localized_plugin/plugin.py b/src/mkdocs_git_revision_date_localized_plugin/plugin.py index 126bee2..6c90a44 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/src/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -144,7 +144,6 @@ def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: return config - def parallel_compute_commit_timestamps(self, files, original_source: Optional[Dict] = None, is_first_commit=False): pool = multiprocessing.Pool(processes=min(10, multiprocessing.cpu_count())) results = [] @@ -154,9 +153,7 @@ def parallel_compute_commit_timestamps(self, files, original_source: Optional[Di # Support plugins like monorep that might have moved the files from the original source that is under git if original_source and abs_src_path in original_source: abs_src_path = original_source[abs_src_path] - result = pool.apply_async( - self.util.get_git_commit_timestamp, args=(abs_src_path, is_first_commit) - ) + result = pool.apply_async(self.util.get_git_commit_timestamp, args=(abs_src_path, is_first_commit)) results.append((abs_src_path, result)) pool.close() pool.join() @@ -173,10 +170,10 @@ def on_files(self, files: Files, config: MkDocsConfig): """ if not self.config.get("enabled") or not self.config.get("enable_parallel_processing"): return - + # Support monorepo/techdocs, which copies the docs_dir to a temporary directory - if "monorepo" in config.get('plugins', {}): - original_source = config.get('plugins').get('monorepo').merger.files_source_dir + if "monorepo" in config.get("plugins", {}): + original_source = config.get("plugins").get("monorepo").merger.files_source_dir else: original_source = None @@ -185,7 +182,6 @@ def on_files(self, files: Files, config: MkDocsConfig): if not self.created_commits: self.parallel_compute_commit_timestamps(files=files, original_source=original_source, is_first_commit=True) - def on_page_markdown(self, markdown: str, page: Page, config: config_options.Config, files, **kwargs) -> str: """ Replace jinja2 tags in markdown and templates with the localized dates. @@ -240,7 +236,9 @@ def on_page_markdown(self, markdown: str, page: Page, config: config_options.Con if getattr(page.file, "generated_by", None): last_revision_hash, last_revision_timestamp = "", int(time.time()) else: - last_revision_hash, last_revision_timestamp = self.last_revision_commits.get(page.file.abs_src_path, (None, None)) + last_revision_hash, last_revision_timestamp = self.last_revision_commits.get( + page.file.abs_src_path, (None, None) + ) if last_revision_timestamp is None: last_revision_hash, last_revision_timestamp = self.util.get_git_commit_timestamp( path=page.file.abs_src_path, @@ -314,8 +312,10 @@ def on_page_markdown(self, markdown: str, page: Page, config: config_options.Con if getattr(page.file, "generated_by", None): first_revision_hash, first_revision_timestamp = "", int(time.time()) else: - first_revision_hash, first_revision_timestamp = self.created_commits.get(page.file.abs_src_path, (None, None)) - if first_revision_timestamp is None: + first_revision_hash, first_revision_timestamp = self.created_commits.get( + page.file.abs_src_path, (None, None) + ) + if first_revision_timestamp is None: first_revision_hash, first_revision_timestamp = self.util.get_git_commit_timestamp( path=page.file.abs_src_path, is_first_commit=True, diff --git a/tests/fixtures/mkdocs-gen-files/gen_pages.py b/tests/fixtures/mkdocs-gen-files/gen_pages.py index 059b0d4..0825cfb 100644 --- a/tests/fixtures/mkdocs-gen-files/gen_pages.py +++ b/tests/fixtures/mkdocs-gen-files/gen_pages.py @@ -2,4 +2,3 @@ with mkdocs_gen_files.open("foo.md", "w") as f: print("Hello, world!", file=f) - diff --git a/tests/fixtures/with_mknotebooks/docs/demo.ipynb b/tests/fixtures/with_mknotebooks/docs/demo.ipynb index 05bcb32..66ee88a 100644 --- a/tests/fixtures/with_mknotebooks/docs/demo.ipynb +++ b/tests/fixtures/with_mknotebooks/docs/demo.ipynb @@ -1,30 +1,4 @@ { - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.3-final" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "Python 3.8.3 64-bit ('base': conda)", - "display_name": "Python 3.8.3 64-bit ('base': conda)", - "metadata": { - "interpreter": { - "hash": "61a9efb557ee20b19fda2d58cb63f9a3bf86c2530fcd43d63aa6e0adea42a5e4" - } - } - } - }, - "nbformat": 4, - "nbformat_minor": 2, "cells": [ { "cell_type": "code", @@ -35,6 +9,7 @@ "import pandas as pd\n", "import numpy as np\n", "import datetime\n", + "\n", "%matplotlib inline" ] }, @@ -44,8 +19,8 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "This notebook last ran at 2020-10-26 15:45:16.815445\n" ] @@ -56,11 +31,11 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "## A df" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -68,8 +43,8 @@ "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
timeamplitude
00.00.000000
10.10.099833
20.20.198669
30.30.295520
40.40.389418
\n
", "text/plain": [ " time amplitude\n", "0 0.0 0.000000\n", @@ -77,39 +52,65 @@ "2 0.2 0.198669\n", "3 0.3 0.295520\n", "4 0.4 0.389418" - ], - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
timeamplitude
00.00.000000
10.10.099833
20.20.198669
30.30.295520
40.40.389418
\n
" + ] }, + "execution_count": 4, "metadata": {}, - "execution_count": 4 + "output_type": "execute_result" } ], "source": [ - "df = pd.DataFrame({\"time\":np.arange(0, 10, 0.1)})\n", - "df[\"amplitude\"] = np.sin(df.time)\n", + "df = pd.DataFrame({\"time\": np.arange(0, 10, 0.1)})\n", + "df[\"amplitude\"] = np.sin(df.time)\n", "df.head(5)" ] }, { - "source": [ - "ax = df.plot()" - ], "cell_type": "code", - "metadata": {}, "execution_count": 5, + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": "
", + "image/png": "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\n", "image/svg+xml": "\n\n\n\n \n \n \n \n 2020-10-26T15:45:51.963649\n image/svg+xml\n \n \n Matplotlib v3.3.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "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\n" + "text/plain": "
" }, "metadata": { "needs_background": "light" - } + }, + "output_type": "display_data" } + ], + "source": [ + "ax = df.plot()" ] } - ] + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.3 64-bit ('base': conda)", + "metadata": { + "interpreter": { + "hash": "61a9efb557ee20b19fda2d58cb63f9a3bf86c2530fcd43d63aa6e0adea42a5e4" + } + }, + "name": "Python 3.8.3 64-bit ('base': conda)" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3-final" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2 } \ No newline at end of file diff --git a/tests/test_builds.py b/tests/test_builds.py index ab6960f..ca3e16a 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -44,6 +44,7 @@ # ########## Helpers ############### # ################################## + @contextmanager def working_directory(path): """ @@ -77,14 +78,9 @@ def get_plugin_config_from_mkdocs(mkdocs_path) -> dict: logging.info("Fixture configuration loaded: " + str(cfg)) if plugin_loaded.config.get("enabled"): - assert ( - plugin_loaded.config.get("locale") is not None - ), "Locale should never be None after plugin is loaded" + assert plugin_loaded.config.get("locale") is not None, "Locale should never be None after plugin is loaded" - logging.info( - "Locale '%s' determined from %s" - % (plugin_loaded.config.get("locale"), mkdocs_path) - ) + logging.info("Locale '%s' determined from %s" % (plugin_loaded.config.get("locale"), mkdocs_path)) return plugin_loaded.config @@ -121,7 +117,7 @@ def setup_clean_mkdocs_folder(mkdocs_yml_path, output_path): shutil.copytree("tests/fixtures/i18n/docs", str(testproject_path / "docs")) else: shutil.copytree("tests/fixtures/basic_project/docs", str(testproject_path / "docs")) - + shutil.copyfile(mkdocs_yml_path, str(testproject_path / "mkdocs.yml")) if "gen-files" in mkdocs_yml_path: @@ -155,24 +151,28 @@ def setup_commit_history(testproject_path): repo.git.checkout("-b", "master") author = "Test Person " - with working_directory(testproject_path): - # page_with_tags contains tags we replace and test if os.path.exists("docs/page_with_tag.md"): repo.git.add("docs/page_with_tag.md") - repo.git.commit(message="add homepage", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 + repo.git.commit( + message="add homepage", author=author, date="1500854705" + ) # Mon Jul 24 2017 00:05:05 GMT+0000 file_name = os.path.join(testproject_path, "docs/page_with_tag.md") with open(file_name, "a") as the_file: the_file.write("test\n") repo.git.add("docs/page_with_tag.md") - repo.git.commit(message="update homepage #1", author=author, date="1525475836") # Fri May 04 2018 23:17:16 GMT+0000 + repo.git.commit( + message="update homepage #1", author=author, date="1525475836" + ) # Fri May 04 2018 23:17:16 GMT+0000 with open(file_name, "a") as the_file: the_file.write("awa\n") repo.git.add("docs/page_with_tag.md") - repo.git.commit(message="update homepage #2", author=author, date="1642911026") # Sun Jan 23 2022 04:10:26 GMT+0000 + repo.git.commit( + message="update homepage #2", author=author, date="1642911026" + ) # Sun Jan 23 2022 04:10:26 GMT+0000 if os.path.exists("docs/page_with_renamed.md"): bf_file_name = os.path.join(testproject_path, "docs/page_with_renamed.md") @@ -181,32 +181,44 @@ def setup_commit_history(testproject_path): if os.path.exists(af_file_name): os.replace(af_file_name, bf_file_name) repo.git.add("docs/page_with_renamed.md") - repo.git.commit(message="page_with_renamed.md before renamed", author=author, date="1655229469") # Tue Jun 14 2022 17:57:49 GMT+0000 + repo.git.commit( + message="page_with_renamed.md before renamed", author=author, date="1655229469" + ) # Tue Jun 14 2022 17:57:49 GMT+0000 repo.git.mv("docs/page_with_renamed.md", "docs/subfolder/page_with_renamed.md") - repo.git.commit(message="page_with_renamed.md after renamed", author=author, date="1655229515") # Tue Jun 14 2022 17:58:35 GMT+0000 + repo.git.commit( + message="page_with_renamed.md after renamed", author=author, date="1655229515" + ) # Tue Jun 14 2022 17:58:35 GMT+0000 if os.path.exists("docs/first_page.md"): repo.git.add("docs/first_page.md") - repo.git.commit(message="first page", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 + repo.git.commit(message="first page", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 file_name = os.path.join(testproject_path, "docs/first_page.md") with open(file_name, "w+") as the_file: the_file.write("Hello\n") repo.git.add("docs/first_page.md") - repo.git.commit(message="first page update 1", author=author, date="1519964705") # Fri Mar 02 2018 04:25:05 GMT+0000 + repo.git.commit( + message="first page update 1", author=author, date="1519964705" + ) # Fri Mar 02 2018 04:25:05 GMT+0000 with open(file_name, "w") as the_file: the_file.write("# First Test Page Edited\n\nSome Lorem text") repo.git.add("docs/first_page.md") - repo.git.commit(message="first page update 2", author=author, date="1643911026") # Thu Feb 03 2022 17:57:06 GMT+0000 + repo.git.commit( + message="first page update 2", author=author, date="1643911026" + ) # Thu Feb 03 2022 17:57:06 GMT+0000 repo.git.add("mkdocs.yml") - repo.git.commit(message="add mkdocs", author=author, date="1500854705 -0700") # Mon Jul 24 2017 00:05:05 GMT+0000 + repo.git.commit( + message="add mkdocs", author=author, date="1500854705 -0700" + ) # Mon Jul 24 2017 00:05:05 GMT+0000 if os.path.exists("docs/second_page.md"): repo.git.add("docs/second_page.md") - repo.git.commit(message="second page", author=author, date="1643911026") # Thu Feb 03 2022 17:57:06 GMT+0000 - + repo.git.commit( + message="second page", author=author, date="1643911026" + ) # Thu Feb 03 2022 17:57:06 GMT+0000 + repo.git.add("docs/index.md") - repo.git.commit(message="homepage", author=author, date="1643911026") # Thu Feb 03 2022 17:57:06 GMT+0000 + repo.git.commit(message="homepage", author=author, date="1643911026") # Thu Feb 03 2022 17:57:06 GMT+0000 return repo @@ -251,21 +263,21 @@ def validate_build(testproject_path, plugin_config: dict = {}): # Make sure with markdown tag has valid # git revision date tag - if not plugin_config.get('enabled'): - return - + if not plugin_config.get("enabled"): + return + page_with_tag = testproject_path / "site/page_with_tag/index.html" contents = page_with_tag.read_text(encoding="utf8") assert re.search(r"renders as\:\s[|\w].+", contents) repo = Util(config=plugin_config, mkdocs_dir=testproject_path) - commit_hash, commit_timestamp =repo.get_git_commit_timestamp( - path=str(testproject_path / "docs/page_with_tag.md"), - is_first_commit=False, - ) + commit_hash, commit_timestamp = repo.get_git_commit_timestamp( + path=str(testproject_path / "docs/page_with_tag.md"), + is_first_commit=False, + ) date_formats = repo.get_date_formats_for_timestamp( commit_timestamp, - locale=plugin_config['locale'], + locale=plugin_config["locale"], add_spans=True, ) @@ -273,11 +285,14 @@ def validate_build(testproject_path, plugin_config: dict = {}): assert any(searches), "No correct revision date formats output was found" if plugin_config.get("enable_creation_date"): - commit_hash, commit_timestamp = repo.get_git_commit_timestamp(path=str(testproject_path / "docs/page_with_tag.md"),is_first_commit=True,) + commit_hash, commit_timestamp = repo.get_git_commit_timestamp( + path=str(testproject_path / "docs/page_with_tag.md"), + is_first_commit=True, + ) assert commit_timestamp == 1500854705 date_formats = repo.get_date_formats_for_timestamp( commit_timestamp=commit_timestamp, - locale=plugin_config['locale'], + locale=plugin_config["locale"], add_spans=True, ) @@ -285,12 +300,12 @@ def validate_build(testproject_path, plugin_config: dict = {}): assert any(searches), "No correct creation date formats output was found" if os.path.exists(str(testproject_path / "docs/subfolder/page_with_renamed.md")): - commit_hash, commit_timestamp=repo.get_git_commit_timestamp( - path=str(testproject_path / "docs/subfolder/page_with_renamed.md"), - is_first_commit=True + commit_hash, commit_timestamp = repo.get_git_commit_timestamp( + path=str(testproject_path / "docs/subfolder/page_with_renamed.md"), is_first_commit=True ) assert commit_timestamp == 1655229469 + def validate_mkdocs_file(temp_path: str, mkdocs_yml_file: str): """ Creates a clean mkdocs project @@ -300,50 +315,45 @@ def validate_mkdocs_file(temp_path: str, mkdocs_yml_file: str): temp_path (PosixPath): Path to temporary folder mkdocs_yml_file (PosixPath): Path to mkdocs.yml file """ - testproject_path = setup_clean_mkdocs_folder( - mkdocs_yml_path=mkdocs_yml_file, output_path=temp_path - ) + testproject_path = setup_clean_mkdocs_folder(mkdocs_yml_path=mkdocs_yml_file, output_path=temp_path) setup_commit_history(testproject_path) result = build_docs_setup(testproject_path) assert result.exit_code == 0, f"'mkdocs build' command failed with output:\n{result.stdout}" # validate build with locale retrieved from mkdocs config file - validate_build( - testproject_path, plugin_config=get_plugin_config_from_mkdocs(mkdocs_yml_file) - ) + validate_build(testproject_path, plugin_config=get_plugin_config_from_mkdocs(mkdocs_yml_file)) return testproject_path - MKDOCS_FILES = [ - 'basic_project/mkdocs_creation_date.yml', - 'basic_project/mkdocs_custom_type.yml', - 'basic_project/mkdocs_datetime.yml', - 'basic_project/mkdocs_exclude.yml', - 'basic_project/mkdocs_fallback_to_build_date.yml', - 'basic_project/mkdocs_locale.yml', - 'basic_project/mkdocs_meta.yml', - 'basic_project/mkdocs_plugin_locale.yml', - 'basic_project/mkdocs.yml', - 'basic_project/mkdocs_theme_timeago_locale.yml', - 'basic_project/mkdocs_theme_language.yml', - 'basic_project/mkdocs_theme_locale_and_language.yml', - 'basic_project/mkdocs_theme_locale_disabled.yml', - 'basic_project/mkdocs_theme_timeago.yml', - 'basic_project/mkdocs_theme_locale.yml', - 'basic_project/mkdocs_theme_no_locale.yml', - 'basic_project/mkdocs_theme_timeago_override.yml', - 'basic_project/mkdocs_theme_timeago_instant.yml', - 'basic_project/mkdocs_timeago_locale.yml', - 'basic_project/mkdocs_timeago.yml', - 'basic_project/mkdocs_with_override.yml', + "basic_project/mkdocs_creation_date.yml", + "basic_project/mkdocs_custom_type.yml", + "basic_project/mkdocs_datetime.yml", + "basic_project/mkdocs_exclude.yml", + "basic_project/mkdocs_fallback_to_build_date.yml", + "basic_project/mkdocs_locale.yml", + "basic_project/mkdocs_meta.yml", + "basic_project/mkdocs_plugin_locale.yml", + "basic_project/mkdocs.yml", + "basic_project/mkdocs_theme_timeago_locale.yml", + "basic_project/mkdocs_theme_language.yml", + "basic_project/mkdocs_theme_locale_and_language.yml", + "basic_project/mkdocs_theme_locale_disabled.yml", + "basic_project/mkdocs_theme_timeago.yml", + "basic_project/mkdocs_theme_locale.yml", + "basic_project/mkdocs_theme_no_locale.yml", + "basic_project/mkdocs_theme_timeago_override.yml", + "basic_project/mkdocs_theme_timeago_instant.yml", + "basic_project/mkdocs_timeago_locale.yml", + "basic_project/mkdocs_timeago.yml", + "basic_project/mkdocs_with_override.yml", # 'i18n/mkdocs.yml' ] INVALID_MKDOCS_FILES = [ - ('basic_project/mkdocs_unknown_type.yml', "AssertionError"), - ('i18n/mkdocs_wrong_order.yml', "should be defined after the i18n plugin in your mkdocs.yml"), + ("basic_project/mkdocs_unknown_type.yml", "AssertionError"), + ("i18n/mkdocs_wrong_order.yml", "should be defined after the i18n plugin in your mkdocs.yml"), ] @@ -357,27 +367,23 @@ def test_tags_are_replaced(tmp_path, mkdocs_file): """ Make sure the {{ }} tags are replaced properly. """ - testproject_path = setup_clean_mkdocs_folder( - mkdocs_yml_path=f"tests/fixtures/{mkdocs_file}", output_path=tmp_path - ) + testproject_path = setup_clean_mkdocs_folder(mkdocs_yml_path=f"tests/fixtures/{mkdocs_file}", output_path=tmp_path) setup_commit_history(testproject_path) result = build_docs_setup(testproject_path) assert result.exit_code == 0, "'mkdocs build' command failed" - plugin_config=get_plugin_config_from_mkdocs(str(testproject_path / "mkdocs.yml")) + plugin_config = get_plugin_config_from_mkdocs(str(testproject_path / "mkdocs.yml")) tags_file = testproject_path / "site/page_with_tag/index.html" contents = tags_file.read_text(encoding="utf8") # validate the build - validate_build( - testproject_path, plugin_config=plugin_config - ) + validate_build(testproject_path, plugin_config=plugin_config) if plugin_config.get("enabled") == False: return True if plugin_config.get("type") == "timeago": - pytest.skip("Not necessary to test the JS library") + pytest.skip("Not necessary to test the JS library") # Make sure count_commits() works # We created 11 commits in setup_commit_history() @@ -385,62 +391,56 @@ def test_tags_are_replaced(tmp_path, mkdocs_file): u = Util(config={}, mkdocs_dir=os.getcwd()) assert commit_count(u._get_repo("docs/page_with_tag.md")) == 11 - # the revision date was in 'setup_commit_history' was set to 1642911026 (Sun Jan 23 2022 04:10:26 GMT+0000) # Assert {{ git_revision_date_localized }} is replaced - date_formats_revision_date = get_date_formats(1642911026, + date_formats_revision_date = get_date_formats( + 1642911026, locale=plugin_config.get("locale"), time_zone=plugin_config.get("timezone"), - custom_format=plugin_config.get("custom_format") + custom_format=plugin_config.get("custom_format"), ) for k, v in date_formats_revision_date.items(): assert v is not None - - date = date_formats_revision_date.get(plugin_config.get('type')) + + date = date_formats_revision_date.get(plugin_config.get("type")) assert re.search(rf"{date}\<\/span.+", contents) # The last site revision was set in setup_commit_history to 1643911026 (Thu Feb 03 2022 17:57:06 GMT+0000) # Assert {{ git_site_revision_date_localized }} is replaced - date_formats_revision_date = get_date_formats(1643911026, + date_formats_revision_date = get_date_formats( + 1643911026, locale=plugin_config.get("locale"), time_zone=plugin_config.get("timezone"), - custom_format=plugin_config.get("custom_format") + custom_format=plugin_config.get("custom_format"), ) for k, v in date_formats_revision_date.items(): assert v is not None - date = date_formats_revision_date.get(plugin_config.get('type')) + date = date_formats_revision_date.get(plugin_config.get("type")) assert re.search(rf"{date}\<\/span.+", contents) # Note {{ git_creation_date_localized }} is only replaced when configured in the config if plugin_config.get("enable_creation_date"): # The creation of page_with_tag.md was set in setup_commit_history to 1500854705 ( Mon Jul 24 2017 00:05:05 GMT+0000 ) - date_formats_revision_date = get_date_formats(1500854705, + date_formats_revision_date = get_date_formats( + 1500854705, locale=plugin_config.get("locale"), time_zone=plugin_config.get("timezone"), - custom_format=plugin_config.get("custom_format") + custom_format=plugin_config.get("custom_format"), ) for k, v in date_formats_revision_date.items(): assert v is not None - date = date_formats_revision_date.get(plugin_config.get('type')) + date = date_formats_revision_date.get(plugin_config.get("type")) assert re.search(rf"{date}\<\/span.+", contents) - - - - def test_git_not_available(tmp_path, recwarn): """ When there is no GIT repo, this should fail """ - testproject_path = setup_clean_mkdocs_folder( - "tests/fixtures/basic_project/mkdocs.yml", tmp_path - ) + testproject_path = setup_clean_mkdocs_folder("tests/fixtures/basic_project/mkdocs.yml", tmp_path) result = build_docs_setup(testproject_path) - assert ( - result.exit_code == 1 - ), "'mkdocs build' command succeeded while there is no GIT repo" + assert result.exit_code == 1, "'mkdocs build' command succeeded while there is no GIT repo" # assert there's a no error when fallback to build date is set to true testproject_path = setup_clean_mkdocs_folder( @@ -450,16 +450,12 @@ def test_git_not_available(tmp_path, recwarn): assert result.exit_code == 0 - - def test_build_material_theme(tmp_path): """ When using mkdocs-material theme, test correct working """ # theme set to 'material' with 'language' set to 'de' - testproject_path = validate_mkdocs_file( - tmp_path, "tests/fixtures/basic_project/mkdocs_theme_language.yml" - ) + testproject_path = validate_mkdocs_file(tmp_path, "tests/fixtures/basic_project/mkdocs_theme_language.yml") # In mkdocs-material, a 'last update' should appear # in German because locale is set to 'de' @@ -473,9 +469,7 @@ def test_material_theme_locale(tmp_path): When using mkdocs-material theme, test correct working """ # theme set to 'material' with 'locale' set to 'de' - testproject_path = validate_mkdocs_file( - tmp_path, "tests/fixtures/basic_project/mkdocs_theme_locale.yml" - ) + testproject_path = validate_mkdocs_file(tmp_path, "tests/fixtures/basic_project/mkdocs_theme_locale.yml") # In mkdocs-material, a 'last update' should appear # in english instead of German because you should use 'language' and not locale. @@ -490,9 +484,7 @@ def test_material_theme_locale_disabled(tmp_path): When using mkdocs-material theme, test correct working """ # theme set to 'material' with 'locale' set to 'de' - testproject_path = validate_mkdocs_file( - tmp_path, "tests/fixtures/basic_project/mkdocs_theme_locale_disabled.yml" - ) + testproject_path = validate_mkdocs_file(tmp_path, "tests/fixtures/basic_project/mkdocs_theme_locale_disabled.yml") # In mkdocs-material, a 'last update' should appear # in english instead of German because you should use 'language' and not locale. @@ -507,9 +499,7 @@ def test_material_theme_no_locale(tmp_path): When using mkdocs-material theme, test correct working """ # theme set to 'material' with 'language' set to 'de' - testproject_path = validate_mkdocs_file( - tmp_path, "tests/fixtures/basic_project/mkdocs_theme_no_locale.yml" - ) + testproject_path = validate_mkdocs_file(tmp_path, "tests/fixtures/basic_project/mkdocs_theme_no_locale.yml") # In mkdocs-material, a 'last update' should appear # in english because default locale is set to 'en' @@ -518,15 +508,14 @@ def test_material_theme_no_locale(tmp_path): assert re.search(r"Last update", contents) - @pytest.mark.parametrize("mkdocs_file, error", INVALID_MKDOCS_FILES) def test_type_unknown(mkdocs_file, error, tmp_path): """ Make sure invalid mkdocs.yml specification raise the correct errors. """ testproject_path = setup_clean_mkdocs_folder( - mkdocs_yml_path=f"tests/fixtures/{ mkdocs_file }", # mkdocs_file, # tmp_path, , - output_path=tmp_path + mkdocs_yml_path=f"tests/fixtures/{mkdocs_file}", # mkdocs_file, # tmp_path, , + output_path=tmp_path, ) # Setup git commit history assert not os.path.exists(str(testproject_path / ".git")) @@ -538,7 +527,7 @@ def test_type_unknown(mkdocs_file, error, tmp_path): with working_directory(testproject_path): # page_with_tags contains tags we replace and test repo.git.add(".") - repo.git.commit(message="add all", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 + repo.git.commit(message="add all", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 result = build_docs_setup(testproject_path) assert result.exit_code == 1 @@ -546,16 +535,12 @@ def test_type_unknown(mkdocs_file, error, tmp_path): assert error in result.stdout or error in str(result.exc_info[0]) - - def test_exclude_pages(tmp_path): """ When using mkdocs-material theme, test correct working """ # theme set to 'material' with 'locale' set to 'de' - testproject_path = validate_mkdocs_file( - tmp_path, "tests/fixtures/basic_project/mkdocs_exclude.yml" - ) + testproject_path = validate_mkdocs_file(tmp_path, "tests/fixtures/basic_project/mkdocs_exclude.yml") # Make sure revision date does not exist in excluded pages first_page = testproject_path / "site/first_page/index.html" @@ -567,16 +552,13 @@ def test_exclude_pages(tmp_path): assert not re.search(r"Last update\:\s[', - "custom": "01. January 1970" + "custom": "01. January 1970", } assert get_date_formats(0) == expected_output @@ -22,7 +21,7 @@ def test_get_dates(): new_expected_output = expected_output.copy() new_expected_output["timeago"] = '' assert get_date_formats(0, locale="en_US") == new_expected_output - + # Test with different locale expected_output = { "date": "1 janvier 1970", @@ -30,7 +29,7 @@ def test_get_dates(): "iso_date": "1970-01-01", "iso_datetime": "1970-01-01 00:00:00", "timeago": '', - "custom": "01. janvier 1970" + "custom": "01. janvier 1970", } assert get_date_formats(0, locale="fr") == expected_output @@ -41,7 +40,7 @@ def test_get_dates(): "iso_date": "1970-01-01", "iso_datetime": "1970-01-01 00:00:00", "timeago": '', - "custom": "01. janeiro 1970" + "custom": "01. janeiro 1970", } assert get_date_formats(0, locale="pt_BR") == expected_output @@ -56,7 +55,7 @@ def test_get_dates(): "iso_date": "1970-01-01", "iso_datetime": "1970-01-01 00:00:00", "timeago": '', - "custom": "01. Jan 1970" + "custom": "01. Jan 1970", } assert get_date_formats(0, locale="en", time_zone="UTC", custom_format="%d. %b %Y") == expected_output @@ -67,7 +66,7 @@ def test_get_dates(): "iso_date": "1970-01-01", "iso_datetime": "1970-01-01 02:00:00", "timeago": '', - "custom": "01. January 1970" + "custom": "01. January 1970", } loc_dt = datetime(1970, 1, 1, 1, 0, 0, tzinfo=get_timezone("Europe/Berlin")) unix_timestamp = loc_dt.replace(tzinfo=timezone.utc).timestamp() @@ -75,7 +74,7 @@ def test_get_dates(): # Test with missing arguments with pytest.raises(TypeError): - get_date_formats() # noqa + get_date_formats() # noqa # Test with invalid timezone with pytest.raises(LookupError): @@ -83,21 +82,20 @@ def test_get_dates(): # Test with more recent date expected_output = { - 'date': 'October 15, 2023', - 'datetime': 'October 15, 2023 13:32:04', - 'iso_date': '2023-10-15', - 'iso_datetime': '2023-10-15 13:32:04', - 'timeago': '', - 'custom': '15. October 2023' + "date": "October 15, 2023", + "datetime": "October 15, 2023 13:32:04", + "iso_date": "2023-10-15", + "iso_datetime": "2023-10-15 13:32:04", + "timeago": '', + "custom": "15. October 2023", } assert get_date_formats(1697369524, time_zone="Europe/Amsterdam") == expected_output - assert get_date_formats(1582397529) == { "date": "February 22, 2020", "datetime": "February 22, 2020 18:52:09", "iso_date": "2020-02-22", "iso_datetime": "2020-02-22 18:52:09", "timeago": '', - "custom": '22. February 2020', - } \ No newline at end of file + "custom": "22. February 2020", + } diff --git a/tests/test_exclude.py b/tests/test_exclude.py index dd19cc9..7f87d3b 100644 --- a/tests/test_exclude.py +++ b/tests/test_exclude.py @@ -1,8 +1,8 @@ from mkdocs_git_revision_date_localized_plugin.exclude import exclude import pytest -def test_exclude(): +def test_exclude(): with pytest.raises(AssertionError): exclude("fsdfs", "not a list") @@ -16,4 +16,4 @@ def test_exclude(): globs = ["folder/*"] assert exclude("folder/index.md", globs) - assert not exclude("subfolder/index.md", globs) \ No newline at end of file + assert not exclude("subfolder/index.md", globs) diff --git a/tests/test_parse_git_ignore_revs.py b/tests/test_parse_git_ignore_revs.py index f22d9fd..7ada34a 100644 --- a/tests/test_parse_git_ignore_revs.py +++ b/tests/test_parse_git_ignore_revs.py @@ -9,9 +9,10 @@ ("\n\n\n\n\nabc123\n\n\n\n\n", ["abc123"]), ] + @pytest.mark.parametrize("test_input,expected", TEST_PARAMS) def test_parse_git_ignore_revs(test_input, expected): - with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False) as fp: + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", delete=False) as fp: fp.write(test_input) temp_file_name = fp.name try: From de5f9f04b914ee0133df132c3d973b67b9bd30f9 Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 10:37:48 +0000 Subject: [PATCH 3/8] use more pathlib --- .../plugin.py | 20 ++++++++++--------- .../util.py | 3 ++- tests/test_builds.py | 15 +++++++------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/mkdocs_git_revision_date_localized_plugin/plugin.py b/src/mkdocs_git_revision_date_localized_plugin/plugin.py index 6c90a44..5062025 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/src/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -10,6 +10,7 @@ import os import time import multiprocessing +from pathlib import Path from mkdocs import __version__ as mkdocs_version from mkdocs.config import config_options @@ -28,7 +29,7 @@ from packaging.version import Version -HERE = os.path.dirname(os.path.abspath(__file__)) +HERE = Path(__file__).parent.absolute() class GitRevisionDateLocalizedPlugin(BasePlugin): @@ -147,12 +148,13 @@ def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: def parallel_compute_commit_timestamps(self, files, original_source: Optional[Dict] = None, is_first_commit=False): pool = multiprocessing.Pool(processes=min(10, multiprocessing.cpu_count())) results = [] - for file in files: - if file.is_documentation_page(): - abs_src_path = file.abs_src_path + for f in files: + if f.is_documentation_page(): + abs_src_path = f.abs_src_path # Support plugins like monorep that might have moved the files from the original source that is under git if original_source and abs_src_path in original_source: abs_src_path = original_source[abs_src_path] + abs_src_path = Path(abs_src_path).absolute() result = pool.apply_async(self.util.get_git_commit_timestamp, args=(abs_src_path, is_first_commit)) results.append((abs_src_path, result)) pool.close() @@ -374,8 +376,8 @@ def on_post_build(self, config: Dict[str, Any], **kwargs) -> None: "js/timeago_mkdocs_material.js", "css/timeago.css", ] - for file in files: - dest_file_path = os.path.join(config["site_dir"], file) - src_file_path = os.path.join(HERE, file) - assert os.path.exists(src_file_path) - copy_file(src_file_path, dest_file_path) + for f in files: + dest_file_path = Path(config["site_dir"]) / f + src_file_path = HERE / f + assert src_file_path.exists() + copy_file(str(src_file_path), str(dest_file_path)) diff --git a/src/mkdocs_git_revision_date_localized_plugin/util.py b/src/mkdocs_git_revision_date_localized_plugin/util.py index 43dbedb..5c43cca 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/util.py +++ b/src/mkdocs_git_revision_date_localized_plugin/util.py @@ -3,6 +3,7 @@ import logging import os import time +from pathlib import Path from mkdocs_git_revision_date_localized_plugin.ci import raise_ci_warnings from mkdocs_git_revision_date_localized_plugin.dates import get_date_formats @@ -34,7 +35,7 @@ def __init__(self, config: Dict, mkdocs_dir: str): self.repo_cache = {} ignore_commits_file = self.config.get("ignored_commits_file") - ignore_commits_filepath = os.path.join(mkdocs_dir, ignore_commits_file) if ignore_commits_file else None + ignore_commits_filepath = Path(mkdocs_dir) / ignore_commits_file if ignore_commits_file else None self.ignored_commits = self.parse_git_ignore_revs(ignore_commits_filepath) if ignore_commits_file else [] def _get_repo(self, path: str) -> Git: diff --git a/tests/test_builds.py b/tests/test_builds.py index ca3e16a..e1a4ef3 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -144,8 +144,7 @@ def setup_commit_history(testproject_path): Returns: repo (repo): git.Repo object """ - assert not os.path.exists(str(testproject_path / ".git")) - testproject_path = str(testproject_path) + assert not (testproject_path / ".git").exists() repo = git.Repo.init(testproject_path, bare=False) repo.git.checkout("-b", "master") @@ -159,7 +158,7 @@ def setup_commit_history(testproject_path): message="add homepage", author=author, date="1500854705" ) # Mon Jul 24 2017 00:05:05 GMT+0000 - file_name = os.path.join(testproject_path, "docs/page_with_tag.md") + file_name = testproject_path / "docs/page_with_tag.md" with open(file_name, "a") as the_file: the_file.write("test\n") repo.git.add("docs/page_with_tag.md") @@ -175,10 +174,10 @@ def setup_commit_history(testproject_path): ) # Sun Jan 23 2022 04:10:26 GMT+0000 if os.path.exists("docs/page_with_renamed.md"): - bf_file_name = os.path.join(testproject_path, "docs/page_with_renamed.md") - af_file_name = os.path.join(testproject_path, "docs/subfolder/page_with_renamed.md") + bf_file_name = testproject_path / "docs/page_with_renamed.md" + af_file_name = testproject_path / "docs/subfolder/page_with_renamed.md" # Since git.mv would actually remove the file, move page_with_renamed.md back to docs if it has been moved - if os.path.exists(af_file_name): + if af_file_name.exists(): os.replace(af_file_name, bf_file_name) repo.git.add("docs/page_with_renamed.md") repo.git.commit( @@ -192,7 +191,7 @@ def setup_commit_history(testproject_path): if os.path.exists("docs/first_page.md"): repo.git.add("docs/first_page.md") repo.git.commit(message="first page", author=author, date="1500854705") # Mon Jul 24 2017 00:05:05 GMT+0000 - file_name = os.path.join(testproject_path, "docs/first_page.md") + file_name = testproject_path / "docs/first_page.md" with open(file_name, "w+") as the_file: the_file.write("Hello\n") repo.git.add("docs/first_page.md") @@ -211,7 +210,7 @@ def setup_commit_history(testproject_path): message="add mkdocs", author=author, date="1500854705 -0700" ) # Mon Jul 24 2017 00:05:05 GMT+0000 - if os.path.exists("docs/second_page.md"): + if Path("docs/second_page.md").exists(): repo.git.add("docs/second_page.md") repo.git.commit( message="second page", author=author, date="1643911026" From 2cf7695adc316713b80b3c3cb1e6cfbe7535218d Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 10:48:36 +0000 Subject: [PATCH 4/8] string paths when parallel processing --- src/mkdocs_git_revision_date_localized_plugin/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mkdocs_git_revision_date_localized_plugin/plugin.py b/src/mkdocs_git_revision_date_localized_plugin/plugin.py index 5062025..6141e23 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/src/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -155,7 +155,8 @@ def parallel_compute_commit_timestamps(self, files, original_source: Optional[Di if original_source and abs_src_path in original_source: abs_src_path = original_source[abs_src_path] abs_src_path = Path(abs_src_path).absolute() - result = pool.apply_async(self.util.get_git_commit_timestamp, args=(abs_src_path, is_first_commit)) + assert abs_src_path.exists() + result = pool.apply_async(self.util.get_git_commit_timestamp, args=(str(abs_src_path), is_first_commit)) results.append((abs_src_path, result)) pool.close() pool.join() From e21cae8afa4b4722220b86f906382c75423e3cd7 Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 10:57:14 +0000 Subject: [PATCH 5/8] ensure string keys --- .../plugin.py | 10 +++++----- tests/fixtures/monorepo/.gitignore | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/monorepo/.gitignore diff --git a/src/mkdocs_git_revision_date_localized_plugin/plugin.py b/src/mkdocs_git_revision_date_localized_plugin/plugin.py index 6141e23..3d99cac 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/src/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -154,9 +154,9 @@ def parallel_compute_commit_timestamps(self, files, original_source: Optional[Di # Support plugins like monorep that might have moved the files from the original source that is under git if original_source and abs_src_path in original_source: abs_src_path = original_source[abs_src_path] - abs_src_path = Path(abs_src_path).absolute() - assert abs_src_path.exists() - result = pool.apply_async(self.util.get_git_commit_timestamp, args=(str(abs_src_path), is_first_commit)) + assert Path(abs_src_path).exists() + abs_src_path = str(Path(abs_src_path).absolute()) + result = pool.apply_async(self.util.get_git_commit_timestamp, args=(abs_src_path, is_first_commit)) results.append((abs_src_path, result)) pool.close() pool.join() @@ -240,7 +240,7 @@ def on_page_markdown(self, markdown: str, page: Page, config: config_options.Con last_revision_hash, last_revision_timestamp = "", int(time.time()) else: last_revision_hash, last_revision_timestamp = self.last_revision_commits.get( - page.file.abs_src_path, (None, None) + str(Path(page.file.abs_src_path).absolute()), (None, None) ) if last_revision_timestamp is None: last_revision_hash, last_revision_timestamp = self.util.get_git_commit_timestamp( @@ -316,7 +316,7 @@ def on_page_markdown(self, markdown: str, page: Page, config: config_options.Con first_revision_hash, first_revision_timestamp = "", int(time.time()) else: first_revision_hash, first_revision_timestamp = self.created_commits.get( - page.file.abs_src_path, (None, None) + str(Path(page.file.abs_src_path).absolute()), (None, None) ) if first_revision_timestamp is None: first_revision_hash, first_revision_timestamp = self.util.get_git_commit_timestamp( diff --git a/tests/fixtures/monorepo/.gitignore b/tests/fixtures/monorepo/.gitignore new file mode 100644 index 0000000..0c98940 --- /dev/null +++ b/tests/fixtures/monorepo/.gitignore @@ -0,0 +1,2 @@ +site/ +build/ \ No newline at end of file From b5b485634996a1a7c8c460251e8d761f8025db13 Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 11:01:15 +0000 Subject: [PATCH 6/8] Better error handling when parallel proc fails --- .../plugin.py | 12 ++++++++---- tests/fixtures/basic_project/mkdocs_no_parallel.yml | 7 +++++++ tests/test_builds.py | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/basic_project/mkdocs_no_parallel.yml diff --git a/src/mkdocs_git_revision_date_localized_plugin/plugin.py b/src/mkdocs_git_revision_date_localized_plugin/plugin.py index 3d99cac..495251b 100644 --- a/src/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/src/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -180,10 +180,14 @@ def on_files(self, files: Files, config: MkDocsConfig): else: original_source = None - if not self.last_revision_commits: - self.parallel_compute_commit_timestamps(files=files, original_source=original_source, is_first_commit=False) - if not self.created_commits: - self.parallel_compute_commit_timestamps(files=files, original_source=original_source, is_first_commit=True) + try: + if not self.last_revision_commits: + self.parallel_compute_commit_timestamps(files=files, original_source=original_source, is_first_commit=False) + if not self.created_commits: + self.parallel_compute_commit_timestamps(files=files, original_source=original_source, is_first_commit=True) + except Exception as e: + logging.warning(f"Parallel processing failed: {str(e)}.\n To fall back to serial processing, use 'enable_parallel_processing: False' setting.") + def on_page_markdown(self, markdown: str, page: Page, config: config_options.Config, files, **kwargs) -> str: """ diff --git a/tests/fixtures/basic_project/mkdocs_no_parallel.yml b/tests/fixtures/basic_project/mkdocs_no_parallel.yml new file mode 100644 index 0000000..8807c99 --- /dev/null +++ b/tests/fixtures/basic_project/mkdocs_no_parallel.yml @@ -0,0 +1,7 @@ +site_name: test gitrevisiondatelocalized_plugin +use_directory_urls: true + +plugins: + - search + - git-revision-date-localized: + enable_parallel_processing: False \ No newline at end of file diff --git a/tests/test_builds.py b/tests/test_builds.py index e1a4ef3..dea8b2f 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -333,6 +333,7 @@ def validate_mkdocs_file(temp_path: str, mkdocs_yml_file: str): "basic_project/mkdocs_fallback_to_build_date.yml", "basic_project/mkdocs_locale.yml", "basic_project/mkdocs_meta.yml", + "basic_project/mkdocs_no_parallel.yml", "basic_project/mkdocs_plugin_locale.yml", "basic_project/mkdocs.yml", "basic_project/mkdocs_theme_timeago_locale.yml", From 26f892109733c73d36ce2359e7cbfad8a453cc2b Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 11:07:34 +0000 Subject: [PATCH 7/8] test with disable --- tests/fixtures/monorepo/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/monorepo/mkdocs.yml b/tests/fixtures/monorepo/mkdocs.yml index 3b9798e..aeaa9f3 100644 --- a/tests/fixtures/monorepo/mkdocs.yml +++ b/tests/fixtures/monorepo/mkdocs.yml @@ -14,5 +14,5 @@ theme: plugins: - search - - git-revision-date-localized + # - git-revision-date-localized - monorepo From a5692df321195ff6969babb3753b3f776822366b Mon Sep 17 00:00:00 2001 From: Tim Vink Date: Thu, 6 Mar 2025 11:57:01 +0000 Subject: [PATCH 8/8] disable monorepo tests on windows --- tests/fixtures/monorepo/mkdocs.yml | 2 +- .../monorepo/mkdocs_reverse_order.yml | 19 +++++++++++++++++++ tests/test_builds.py | 15 +++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/monorepo/mkdocs_reverse_order.yml diff --git a/tests/fixtures/monorepo/mkdocs.yml b/tests/fixtures/monorepo/mkdocs.yml index aeaa9f3..3b9798e 100644 --- a/tests/fixtures/monorepo/mkdocs.yml +++ b/tests/fixtures/monorepo/mkdocs.yml @@ -14,5 +14,5 @@ theme: plugins: - search - # - git-revision-date-localized + - git-revision-date-localized - monorepo diff --git a/tests/fixtures/monorepo/mkdocs_reverse_order.yml b/tests/fixtures/monorepo/mkdocs_reverse_order.yml new file mode 100644 index 0000000..c341095 --- /dev/null +++ b/tests/fixtures/monorepo/mkdocs_reverse_order.yml @@ -0,0 +1,19 @@ +site_name: Cats API + +nav: + - Intro: 'index.md' + - Authentication: 'authentication.md' + - Components: '*include ./components/*/mkdocs.yml' + - API: + - v1: '!include ./v1/mkdocs.yml' + - v2: '!include ./v2/mkdocs.yml' + - v3: '!include ./v3/mkdocs.yml' + +theme: + name: material + +plugins: + - search + # Note; here monorepo is defined after git-revision-date-localized + - monorepo + - git-revision-date-localized diff --git a/tests/test_builds.py b/tests/test_builds.py index dea8b2f..a85022e 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -13,6 +13,7 @@ # standard lib import logging import os +import sys import re import shutil from contextlib import contextmanager @@ -680,14 +681,16 @@ def test_ignored_commits(tmp_path): assert "May 4, 2018" in contents +@pytest.mark.skipif(sys.platform.startswith("win"), reason="monorepo plugin did not work for me on windows (even without this plugin)") def test_monorepo_compat(tmp_path): testproject_path = setup_clean_mkdocs_folder("tests/fixtures/monorepo/mkdocs.yml", tmp_path) - repo = setup_commit_history(testproject_path) + setup_commit_history(testproject_path) result = build_docs_setup(testproject_path) + assert result.exit_code == 0, f"'mkdocs build' command failed with:\n\n{result.stdout}" - # author = "Test Person " - # with working_directory(testproject_path): - # repo.git.add(".") - # repo.git.commit(message="add all", author=author, date="1500854705") - +@pytest.mark.skipif(sys.platform.startswith("win"), reason="monorepo plugin did not work for me on windows (even without this plugin)") +def test_monorepo_compat_reverse_order(tmp_path): + testproject_path = setup_clean_mkdocs_folder("tests/fixtures/monorepo/mkdocs_reverse_order.yml", tmp_path) + setup_commit_history(testproject_path) + result = build_docs_setup(testproject_path) assert result.exit_code == 0, f"'mkdocs build' command failed with:\n\n{result.stdout}"