diff --git a/mkdocs_git_revision_date_localized_plugin/plugin.py b/mkdocs_git_revision_date_localized_plugin/plugin.py index 53198b6..6ed10b7 100644 --- a/mkdocs_git_revision_date_localized_plugin/plugin.py +++ b/mkdocs_git_revision_date_localized_plugin/plugin.py @@ -1,4 +1,5 @@ # standard lib +import logging import re # 3rd party @@ -7,18 +8,17 @@ from mkdocs.structure.nav import Page # package modules -from .util import Util +from mkdocs_git_revision_date_localized_plugin.util import Util class GitRevisionDateLocalizedPlugin(BasePlugin): config_scheme = ( ("fallback_to_build_date", config_options.Type(bool, default=False)), - ("locale", config_options.Type(str, default="")), + ("locale", config_options.Type(str, default=None)), ("type", config_options.Type(str, default="date")), ) def __init__(self): - self.locale = "en" self.util = Util() def on_config(self, config: config_options.Config) -> dict: @@ -37,24 +37,56 @@ def on_config(self, config: config_options.Config) -> dict: dict: global configuration object """ - # Get locale settings - mkdocs_locale = config.get("locale", "") - plugin_locale = self.config["locale"] - theme_locale = vars(config["theme"]).get("_vars", {}).get("locale", "") - if theme_locale == "": - theme_locale = vars(config["theme"]).get("_vars", {}).get("language", "") + # Get locale settings - might be added in future mkdocs versions + # see: https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/issues/24 + mkdocs_locale = config.get("locale", None) + + # Get locale from plugin configuration + plugin_locale = self.config.get("locale", None) + + # theme locale + if "theme" in config and "locale" in config.get("theme"): + custom_theme = config.get("theme") + theme_locale = custom_theme._vars.get("locale") + logging.debug( + "Locale '%s' extracted from the custom theme: '%s'" + % (theme_locale, custom_theme.name) + ) + elif "theme" in config and "language" in config.get("theme"): + custom_theme = config.get("theme") + theme_locale = custom_theme._vars.get("language") + logging.debug( + "Locale '%s' extracted from the custom theme: '%s'" + % (theme_locale, custom_theme.name) + ) + + else: + theme_locale = None + logging.debug( + "No locale found in theme configuration (or no custom theme set)" + ) # First prio: plugin locale - if plugin_locale != "": - self.locale = plugin_locale + if plugin_locale: + locale_set = plugin_locale + logging.debug("Using locale from plugin configuration: %s" % locale_set) # Second prio: theme locale - elif theme_locale != "": - self.locale = theme_locale + elif theme_locale: + locale_set = theme_locale + logging.debug( + "Locale not set in plugin. Fallback to theme configuration: %s" + % locale_set + ) # Third prio is mkdocs locale (which might be added in the future) - elif mkdocs_locale != "": - self.locale = mkdocs_locale + elif mkdocs_locale: + locale_set = mkdocs_locale + logging.debug("Using locale from mkdocs configuration: %s" % locale_set) else: - self.locale = "en" + locale_set = "en" + logging.debug("No locale set. Fallback to: %s" % locale_set) + + # set locale also in plugin configuration + self.config["locale"] = locale_set return config @@ -78,7 +110,7 @@ def on_post_page(self, output_content: str, **kwargs) -> str: str: output of rendered template as string """ - if self.config["type"] != "timeago": + if self.config.get("type") != "timeago": return output_content extra_js = """ @@ -118,7 +150,7 @@ def on_page_markdown( revision_dates = self.util.get_revision_date_for_file( path=page.file.abs_src_path, - locale=self.locale, + locale=self.config.get("locale", "en"), fallback_to_build_date=self.config.get("fallback_to_build_date"), ) revision_date = revision_dates[self.config["type"]] @@ -129,3 +161,11 @@ def on_page_markdown( markdown, flags=re.IGNORECASE, ) + + +# ############################################################################## +# ##### Stand alone program ######## +# ################################## +if __name__ == "__main__": + """Standalone execution and quick tests.""" + pass diff --git a/mkdocs_git_revision_date_localized_plugin/util.py b/mkdocs_git_revision_date_localized_plugin/util.py index 2f04261..d7880b0 100644 --- a/mkdocs_git_revision_date_localized_plugin/util.py +++ b/mkdocs_git_revision_date_localized_plugin/util.py @@ -56,6 +56,7 @@ def _date_formats(unix_timestamp: float, locale="en") -> dict: timestamp_in_ms = unix_timestamp * 1000 revision_date = datetime.utcfromtimestamp(unix_timestamp) + logging.debug("Revision date: %s - Locale: %s" % (revision_date, locale)) return { "date": format_date(revision_date, format="long", locale=locale), @@ -118,7 +119,7 @@ def get_revision_date_for_file( unix_timestamp = datetime.utcnow().timestamp() logging.warning("%s has no git logs, using current timestamp" % path) - return self._date_formats(unix_timestamp) + return self._date_formats(unix_timestamp=unix_timestamp, locale=locale) def is_shallow_clone(repo: Git) -> bool: diff --git a/tests/basic_setup/mkdocs_datetime.yml b/tests/basic_setup/mkdocs_datetime.yml index d839cff..8de0d2b 100644 --- a/tests/basic_setup/mkdocs_datetime.yml +++ b/tests/basic_setup/mkdocs_datetime.yml @@ -4,4 +4,4 @@ use_directory_urls: true plugins: - search - git-revision-date-localized: - type: datetime \ No newline at end of file + type: datetime diff --git a/tests/basic_setup/mkdocs_locale.yml b/tests/basic_setup/mkdocs_locale.yml index 51bd56b..ef0d81a 100644 --- a/tests/basic_setup/mkdocs_locale.yml +++ b/tests/basic_setup/mkdocs_locale.yml @@ -1,7 +1,7 @@ site_name: test gitrevisiondatelocalized_plugin use_directory_urls: true -locale: nl +locale: fr plugins: - search diff --git a/tests/basic_setup/mkdocs_plugin_locale.yml b/tests/basic_setup/mkdocs_plugin_locale.yml index b714e06..0bc9fc7 100644 --- a/tests/basic_setup/mkdocs_plugin_locale.yml +++ b/tests/basic_setup/mkdocs_plugin_locale.yml @@ -4,4 +4,4 @@ use_directory_urls: true plugins: - search - git-revision-date-localized: - locale: nl \ No newline at end of file + locale: nl diff --git a/tests/basic_setup/mkdocs_theme_no_locale.yml b/tests/basic_setup/mkdocs_theme_no_locale.yml new file mode 100644 index 0000000..38a543c --- /dev/null +++ b/tests/basic_setup/mkdocs_theme_no_locale.yml @@ -0,0 +1,9 @@ +site_name: test gitrevisiondatelocalized_plugin +use_directory_urls: true + +theme: + name: 'material' + +plugins: + - search + - git-revision-date-localized \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index b352566..6f4ea39 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -9,25 +9,60 @@ >>> os.mkdir(tmp_path) """ +# ############################################################################# +# ########## Libraries ############# +# ################################## + # standard lib import logging import os import re import shutil -# 3rd partu +# MkDocs +from mkdocs.__main__ import build_command +from mkdocs.config import load_config + +# other 3rd party import git import pytest -import yaml from click.testing import CliRunner -from mkdocs.__main__ import build_command # package module from mkdocs_git_revision_date_localized_plugin.util import Util +# ############################################################################# +# ######## Globals ################# +# ################################## + +PLUGIN_NAME = "git-revision-date-localized" -def load_config(mkdocs_path): - return yaml.load(open(mkdocs_path, "rb"), Loader=yaml.Loader) +# custom log level to get plugin info messages +logging.basicConfig(level=logging.INFO) + + +# ############################################################################# +# ########## Helpers ############### +# ################################## +def get_plugin_config_from_mkdocs(mkdocs_path) -> dict: + # instanciate plugin + cfg_mkdocs = load_config(mkdocs_path) + + plugins = cfg_mkdocs.get("plugins") + plugin_loaded = plugins.get(PLUGIN_NAME) + + cfg = plugin_loaded.on_config(cfg_mkdocs) + logging.info("Fixture configuration loaded: " + str(cfg)) + + 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) + ) + return plugin_loaded.config def setup_clean_mkdocs_folder(mkdocs_yml_path, output_path): @@ -141,7 +176,7 @@ def build_docs_setup(testproject_path): raise -def validate_build(testproject_path): +def validate_build(testproject_path, plugin_config: dict): """ Validates a build from a testproject @@ -157,19 +192,21 @@ def validate_build(testproject_path): # Make sure with markdown tag has valid # git revision date tag page_with_tag = testproject_path / "site/page_with_tag/index.html" - contents = page_with_tag.read_text() + contents = page_with_tag.read_text(encoding="utf8") assert re.search(r"Markdown tag\:\s[|\w].+", contents) repo = Util(testproject_path) date_formats = repo.get_revision_date_for_file( - testproject_path / "docs/page_with_tag.md" + path=testproject_path / "docs/page_with_tag.md", + locale=plugin_config.get("locale"), + fallback_to_build_date=plugin_config.get("fallback_to_build_date"), ) searches = [re.search(x, contents) for x in date_formats.values()] assert any(searches), "No correct date formats output was found" -def validate_mkdocs_file(temp_path, mkdocs_yml_file): +def validate_mkdocs_file(temp_path: str, mkdocs_yml_file: str): """ Creates a clean mkdocs project for a mkdocs YML file, builds and validates it. @@ -184,14 +221,18 @@ def validate_mkdocs_file(temp_path, mkdocs_yml_file): setup_commit_history(testproject_path) result = build_docs_setup(testproject_path) assert result.exit_code == 0, "'mkdocs build' command failed" - validate_build(testproject_path) - - return testproject_path + # validate build with locale retrieved from mkdocs config file + validate_build( + testproject_path, plugin_config=get_plugin_config_from_mkdocs(mkdocs_yml_file) + ) -#### Tests #### + return testproject_path +# ############################################################################# +# ########### Tests ################ +# ################################## def test_date_formats(): u = Util() assert u._date_formats(1582397529) == { @@ -217,19 +258,6 @@ def test_missing_git_repo(tmp_path): ), "'mkdocs build' command succeeded while there is no GIT repo" -def test_missing_git_repo_ignored(tmp_path): - """ - When there is no GIT repo, the git error should be ignored. - """ - testproject_path = setup_clean_mkdocs_folder( - mkdocs_yml_path="tests/basic_setup/mkdocs_fallback_to_build_date.yml", - output_path=tmp_path, - ) - - result = build_docs_setup(testproject_path) - assert result.exit_code == 0 - - def test_build_no_options(tmp_path): # Enable plugin with no extra options set validate_mkdocs_file(tmp_path, "tests/basic_setup/mkdocs.yml") @@ -241,7 +269,7 @@ def test_build_locale_plugin(tmp_path): def test_build_locale_mkdocs(tmp_path): - # Enable plugin with mkdocs locale set to 'nl' + # Enable plugin with mkdocs locale set to 'fr' validate_mkdocs_file(tmp_path, "tests/basic_setup/mkdocs_locale.yml") @@ -257,10 +285,25 @@ def test_material_theme(tmp_path): # In mkdocs-material, a 'last update' should appear # in German because locale is set to 'de' index_file = testproject_path / "site/index.html" - contents = index_file.read_text() + contents = index_file.read_text(encoding="utf8") assert re.search(r"Letztes Update\:\s[\w].+", contents) +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/basic_setup/mkdocs_theme_no_locale.yml" + ) + + # In mkdocs-material, a 'last update' should appear + # in German because locale is set to 'de' + index_file = testproject_path / "site/index.html" + contents = index_file.read_text(encoding="utf8") + assert re.search(r"Last update\:\s[\w].+", contents) + def test_type_timeago(tmp_path): # type: 'timeago' testproject_path = validate_mkdocs_file( diff --git a/tests/test_plugin.py b/tests/test_plugin.py new file mode 100644 index 0000000..3d60de8 --- /dev/null +++ b/tests/test_plugin.py @@ -0,0 +1,216 @@ +#! python3 # noqa E265 + +""" + Test for the plugin class (subclass of mkdocs.BasePlugin). + + Usage from the repo root folder: + + # all tests + python -m unittest tests.test_plugin + + # specific test + python -m unittest tests.test_plugin.TestMkdocsPlugin. +""" + +# ############################################################################# +# ########## Libraries ############# +# ################################## + +# Standard library +from pathlib import Path +import logging +import unittest + +# MkDocs +from mkdocs.config import load_config + +# package +from mkdocs_git_revision_date_localized_plugin.plugin import ( + GitRevisionDateLocalizedPlugin, +) + + +# ############################################################################# +# ######## Globals ################# +# ################################## + +# make this test module easily reusable +PLUGIN_NAME = "git-revision-date-localized" + +# custom log level to get plugin info messages +logging.basicConfig(level=logging.INFO) + +# ############################################################################# +# ########## Helpers ############### +# ################################## + + +# ############################################################################# +# ########## Classes ############### +# ################################## + + +class TestMkdocsPlugin(unittest.TestCase): + """MkDocs plugin module.""" + + # -- Standard methods -------------------------------------------------------- + @classmethod + def setUpClass(cls): + """Executed when module is loaded before any test.""" + cls.fixtures_mkdocs_config_files = sorted( + Path("tests/basic_setup").glob("*.yml") + ) + + cls.fixtures_config_cases_ok = { + "default_explicit": { + "type": "date", + "locale": "en", + "fallback_to_build_date": False, + }, + # locale variations + "default_no_locale": {"type": "date", "fallback_to_build_date": False}, + "custom_locale": {"locale": "fr"}, + # type variations + "type_datetime": {"type": "datetime"}, + "type_iso_date": {"type": "iso_date"}, + "type_iso_datetime": {"type": "iso_datetime"}, + "type_timeago": {"type": "timeago"}, + # falbback variations + "fallback_true": {"fallback_to_build_date": True}, + } + + cls.fixtures_config_cases_bad = { + "invalid_option_name": {"language": "en",}, + # "invalid_value": {"type": "calendar", "locale": "nl"}, + "invalid_value_type": {"type": 1, "locale": "de"}, + } + + def setUp(self): + """Executed before each test.""" + pass + + def tearDown(self): + """Executed after each test.""" + pass + + @classmethod + def tearDownClass(cls): + """Executed after the last test.""" + pass + + # -- TESTS --------------------------------------------------------- + + # -- GET -- + def test_plugin_instanciation(self): + """Simple test plugin instanciation""" + # instanciate + plg = GitRevisionDateLocalizedPlugin() + + # default values + self.assertIsInstance(plg.config, dict) + self.assertEqual(plg.config, {}) + + def test_plugin_load_configs_ok(self): + """Test inherited plugin load_config method on good configurations""" + # instanciate + plg = GitRevisionDateLocalizedPlugin() + + # parse fixtures configurations alone + for i in self.fixtures_config_cases_ok: + cfg = self.fixtures_config_cases_ok.get(i) + out_cfg = plg.load_config(options=cfg) + + # check if config loader returned no errors + self.assertIsInstance(out_cfg, tuple) + [self.assertListEqual(v, []) for v in out_cfg] + self.assertEqual(all([len(i) == 0 for i in out_cfg]), True) + + # try associating mkdocs configuration + for i in self.fixtures_mkdocs_config_files: + out_cfg_mkdocs = plg.load_config( + options=cfg, config_file_path=str(i.resolve()) + ) + + # check if config loader returned no errors + self.assertIsInstance(out_cfg_mkdocs, tuple) + [self.assertListEqual(v, []) for v in out_cfg_mkdocs] + self.assertEqual(all([len(i) == 0 for i in out_cfg_mkdocs]), True) + + def test_plugin_load_configs_bad(self): + """Test inherited plugin load_config method on bad configurations""" + # instanciate + plg = GitRevisionDateLocalizedPlugin() + + # simulate a complete configuration + for i in self.fixtures_config_cases_bad: + cfg = self.fixtures_config_cases_bad.get(i) + out_cfg = plg.load_config(options=cfg) + + # check if config loader returned no errors + self.assertIsInstance(out_cfg, tuple) + self.assertEqual(all([len(i) == 0 for i in out_cfg]), False) + + # try associating mkdocs configuration + for i in self.fixtures_mkdocs_config_files: + out_cfg_mkdocs = plg.load_config( + options=cfg, config_file_path=str(i.resolve()) + ) + + # check if config loader returned no errors + self.assertIsInstance(out_cfg_mkdocs, tuple) + self.assertEqual(all([len(i) == 0 for i in out_cfg_mkdocs]), False) + + def test_plugin_on_config(self): + """Test inherited plugin on_config method""" + # load try associating mkdocs configuration + for i in self.fixtures_mkdocs_config_files: + # logging.info("Using Mkdocs configuration: %s " % i.resolve()) + cfg_mkdocs = load_config(str(i)) + + # get mkdocs locale config - expected as future feature + mkdocs_locale = cfg_mkdocs.get("locale", None) + + # get our plugin config and copy it + plugin_loaded_from_mkdocs = cfg_mkdocs.get("plugins").get(PLUGIN_NAME) + cfg_before_on_config = plugin_loaded_from_mkdocs.config.copy() + + # get theme configuration + theme = cfg_mkdocs.get("theme") # -> Theme + + # look for the theme locale/language + if "locale" in theme._vars: + theme_locale = theme._vars.get("locale") + elif "language" in theme._vars: + theme_locale = theme._vars.get("language") + else: + theme_locale = None + + # execute on_config with global mkdocs loaded configuration and save config + plugin_loaded_from_mkdocs.on_config(cfg_mkdocs) + cfg_after_on_config = plugin_loaded_from_mkdocs.config.copy() + + # -- CASES for LOCALE --------------------------------------- + result_locale = cfg_after_on_config.get("locale") + # if locale set in plugin configuration = it the one! + if cfg_before_on_config.get("locale"): + self.assertEqual(result_locale, cfg_before_on_config.get("locale")) + # if locale set in theme: it should be used + elif theme_locale and not cfg_before_on_config.get("locale"): + self.assertEqual(result_locale, theme_locale) + # if locale not set in plugin nor in theme but in mkdocs = mkdocs + elif ( + mkdocs_locale + and not cfg_before_on_config.get("locale") + and not theme_locale + ): + self.assertEqual(result_locale, mkdocs_locale) + # if locale is not set at all = default = "en" + else: + self.assertEqual(result_locale, "en") + + +# ############################################################################## +# ##### Stand alone program ######## +# ################################## +if __name__ == "__main__": + unittest.main()