diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index 39cf6ba..ea2e9da 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -8,11 +8,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: '0' - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/scheduled_unittests.yml b/.github/workflows/scheduled_unittests.yml index e4e8774..9a01051 100644 --- a/.github/workflows/scheduled_unittests.yml +++ b/.github/workflows/scheduled_unittests.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Python uses: actions/setup-python@master with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 5d57fcd..2ec52b1 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -5,11 +5,11 @@ jobs: name: Run unit tests with codecov upload runs-on: ${{ matrix.os }} env: - USING_COVERAGE: '3.7' + USING_COVERAGE: '3.10' strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, "3.10"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@master diff --git a/.github/workflows/unittests_codecov.yml b/.github/workflows/unittests_codecov.yml index b3a9d07..1e07c34 100644 --- a/.github/workflows/unittests_codecov.yml +++ b/.github/workflows/unittests_codecov.yml @@ -13,11 +13,11 @@ jobs: name: Run unit tests with codecov upload runs-on: ${{ matrix.os }} env: - USING_COVERAGE: '3.7' + USING_COVERAGE: '3.10' strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, "3.10"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@master diff --git a/mkdocs_git_revision_date_localized_plugin/dates.py b/mkdocs_git_revision_date_localized_plugin/dates.py new file mode 100644 index 0000000..17266fd --- /dev/null +++ b/mkdocs_git_revision_date_localized_plugin/dates.py @@ -0,0 +1,47 @@ + +from babel.dates import format_date, get_timezone + +from datetime import datetime, timezone +from typing import Any, Dict + + +def get_date_formats( + unix_timestamp: float, + locale: str = "en", + time_zone: str = "UTC", + custom_format: str = "%d. %B %Y" +) -> Dict[str, Any]: + """ + Calculate different date formats / types. + + Args: + unix_timestamp (float): A timestamp in seconds since 1970. Assumes UTC. + locale (str): Locale code of language to use. Defaults to 'en'. + time_zone (str): Timezone database name (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). + custom_format (str): strftime format specifier for the 'custom' type + + Returns: + dict: Different date formats. + """ + assert time_zone is not None + assert locale is not None + + utc_revision_date = datetime.fromtimestamp(int(unix_timestamp), tz=timezone.utc) + loc_revision_date = utc_revision_date.replace( + tzinfo=get_timezone("UTC") + ).astimezone(get_timezone(time_zone)) + + return { + "date": format_date(loc_revision_date, format="long", locale=locale), + "datetime": " ".join( + [ + format_date(loc_revision_date, format="long", locale=locale), + loc_revision_date.strftime("%H:%M:%S"), + ] + ), + "iso_date": loc_revision_date.strftime("%Y-%m-%d"), + "iso_datetime": loc_revision_date.strftime("%Y-%m-%d %H:%M:%S"), + "timeago": '' % (loc_revision_date.isoformat(), locale), + "custom": loc_revision_date.strftime(custom_format), + } + \ No newline at end of file diff --git a/mkdocs_git_revision_date_localized_plugin/util.py b/mkdocs_git_revision_date_localized_plugin/util.py index 370a3d3..1cbb474 100644 --- a/mkdocs_git_revision_date_localized_plugin/util.py +++ b/mkdocs_git_revision_date_localized_plugin/util.py @@ -2,11 +2,11 @@ import logging import os import time -from datetime import datetime from mkdocs_git_revision_date_localized_plugin.ci import raise_ci_warnings +from mkdocs_git_revision_date_localized_plugin.dates import get_date_formats + -from babel.dates import format_date, get_timezone from git import ( Repo, Git, @@ -16,7 +16,7 @@ NoSuchPathError, ) -from typing import Any, Dict +from typing import Dict logger = logging.getLogger("mkdocs.plugins") @@ -44,43 +44,6 @@ def _get_repo(self, path: str) -> Git: return self.repo_cache[path] - @staticmethod - def _date_formats( - unix_timestamp: float, locale: str = "en", time_zone: str = "UTC", custom_format: str = "%d. %B %Y" - ) -> Dict[str, Any]: - """ - Calculate different date formats / types. - - Args: - unix_timestamp (float): A timestamp in seconds since 1970. - locale (str): Locale code of language to use. Defaults to 'en'. - time_zone (str): Timezone database name (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). - custom_format (str): strftime format specifier for the 'custom' type - - Returns: - dict: Different date formats. - """ - assert time_zone is not None - assert locale is not None - - utc_revision_date = datetime.utcfromtimestamp(int(unix_timestamp)) - loc_revision_date = utc_revision_date.replace( - tzinfo=get_timezone("UTC") - ).astimezone(get_timezone(time_zone)) - - return { - "date": format_date(loc_revision_date, format="long", locale=locale), - "datetime": " ".join( - [ - format_date(loc_revision_date, format="long", locale=locale), - loc_revision_date.strftime("%H:%M:%S"), - ] - ), - "iso_date": loc_revision_date.strftime("%Y-%m-%d"), - "iso_datetime": loc_revision_date.strftime("%Y-%m-%d %H:%M:%S"), - "timeago": '' % (loc_revision_date.isoformat(), locale), - "custom": loc_revision_date.strftime(custom_format), - } def get_git_commit_timestamp( self, @@ -201,7 +164,7 @@ def get_date_formats_for_timestamp( Returns: dict: Localized date variants. """ - date_formats = self._date_formats( + date_formats = get_date_formats( unix_timestamp=commit_timestamp, time_zone=self.config.get("timezone"), locale=locale, diff --git a/tests/fixtures/i18n/mkdocs.yml b/tests/fixtures/i18n/mkdocs.yml index f15690b..f3e44c7 100644 --- a/tests/fixtures/i18n/mkdocs.yml +++ b/tests/fixtures/i18n/mkdocs.yml @@ -22,22 +22,13 @@ markdown_extensions: plugins: - search - i18n: - default_language: !ENV [DEFAULT_LANGUAGE, "en"] - default_language_only: !ENV [DEFAULT_LANGUAGE_ONLY, false] languages: - default: - name: Default (en) - build: true - en: + - locale: en name: English build: true - site_name: "MkDocs static i18n plugin demo (en)" - fr: + default: true + - locale: fr name: Français build: true - site_name: "Démo du plugin MkDocs static i18n (fr)" - nav_translations: - fr: - Topic1: Sujet1 - Topic2: Sujet2 + - git-revision-date-localized \ No newline at end of file diff --git a/tests/fixtures/i18n/mkdocs_wrong_order.yml b/tests/fixtures/i18n/mkdocs_wrong_order.yml index c2f7ccd..ba24236 100644 --- a/tests/fixtures/i18n/mkdocs_wrong_order.yml +++ b/tests/fixtures/i18n/mkdocs_wrong_order.yml @@ -27,21 +27,13 @@ plugins: - search - git-revision-date-localized - i18n: - default_language: !ENV [DEFAULT_LANGUAGE, "en"] - default_language_only: !ENV [DEFAULT_LANGUAGE_ONLY, false] languages: - default: - name: Default (en) - build: true - en: + - locale: en name: English build: true - site_name: "MkDocs static i18n plugin demo (en)" - fr: + default: true + - locale: fr name: Français build: true - site_name: "Démo du plugin MkDocs static i18n (fr)" - nav_translations: - fr: - Topic1: Sujet1 - Topic2: Sujet2 + + diff --git a/tests/test_builds.py b/tests/test_builds.py index 5af31c6..de1387f 100644 --- a/tests/test_builds.py +++ b/tests/test_builds.py @@ -30,6 +30,7 @@ # package module from mkdocs_git_revision_date_localized_plugin.util import Util from mkdocs_git_revision_date_localized_plugin.ci import commit_count +from mkdocs_git_revision_date_localized_plugin.dates import get_date_formats # ################################## # ######## Globals ################# @@ -147,6 +148,7 @@ def setup_commit_history(testproject_path): testproject_path = str(testproject_path) repo = git.Repo.init(testproject_path, bare=False) + repo.git.checkout("-b", "master") author = "Test Person " @@ -342,18 +344,6 @@ def validate_mkdocs_file(temp_path: str, mkdocs_yml_file: str): # ################################## -def test_date_formats(): - u = Util() - assert u._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', - } - - @pytest.mark.parametrize("mkdocs_file", MKDOCS_FILES, ids=lambda x: f"mkdocs file: {x}") def test_tags_are_replaced(tmp_path, mkdocs_file): """ @@ -390,7 +380,7 @@ def test_tags_are_replaced(tmp_path, mkdocs_file): # 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 = Util()._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") @@ -403,7 +393,7 @@ def test_tags_are_replaced(tmp_path, mkdocs_file): # 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 = Util()._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") @@ -416,7 +406,7 @@ def test_tags_are_replaced(tmp_path, mkdocs_file): # 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 = Util()._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") @@ -628,6 +618,10 @@ def test_low_fetch_depth(tmp_path, caplog): # Clone the local repo with fetch depth of 1 repo = git.Repo.init(cloned_folder, bare=False) + try: + repo.heads.main.rename("master", force=True) + except: + pass origin = repo.create_remote("origin", str(testproject_path)) origin.fetch(depth=1, prune=True) repo.create_head( diff --git a/tests/test_dates.py b/tests/test_dates.py new file mode 100644 index 0000000..bbe0e94 --- /dev/null +++ b/tests/test_dates.py @@ -0,0 +1,72 @@ +import pytest +from datetime import datetime, timezone +from babel.dates import get_timezone + +from mkdocs_git_revision_date_localized_plugin.dates import get_date_formats + + + +def test_get_dates(): + # Test with default arguments + expected_output = { + "date": "January 1, 1970", + "datetime": "January 1, 1970 00:00:00", + "iso_date": "1970-01-01", + "iso_datetime": "1970-01-01 00:00:00", + "timeago": '', + "custom": "01. January 1970" + } + assert get_date_formats(0) == expected_output + + # Test with custom arguments + expected_output = { + "date": "January 1, 1970", + "datetime": "January 1, 1970 00:00:00", + "iso_date": "1970-01-01", + "iso_datetime": "1970-01-01 00:00:00", + "timeago": '', + "custom": "01. Jan 1970" + } + assert get_date_formats(0, locale="en", time_zone="UTC", custom_format="%d. %b %Y") == expected_output + + # Test with non-UTC timezone + expected_output = { + "date": "January 1, 1970", + "datetime": "January 1, 1970 02:00:00", + "iso_date": "1970-01-01", + "iso_datetime": "1970-01-01 02:00:00", + "timeago": '', + "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() + assert get_date_formats(unix_timestamp, time_zone="Europe/Berlin") == expected_output + + # Test with missing arguments + with pytest.raises(TypeError): + get_date_formats() # noqa + + # Test with invalid timezone + with pytest.raises(LookupError): + get_date_formats(0, time_zone="Invalid/Timezone") + + # 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' + } + 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 diff --git a/tests/test_plugin.py b/tests/test_plugin.py deleted file mode 100644 index 3d60de8..0000000 --- a/tests/test_plugin.py +++ /dev/null @@ -1,216 +0,0 @@ -#! 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()