diff --git a/mkdocs_rss_plugin/integrations/theme_material_base.py b/mkdocs_rss_plugin/integrations/theme_material_base.py new file mode 100644 index 0000000..ee01522 --- /dev/null +++ b/mkdocs_rss_plugin/integrations/theme_material_base.py @@ -0,0 +1,89 @@ +#! python3 # noqa: E265 + +# ############################################################################ +# ########## Libraries ############# +# ################################## + +# standard library +from typing import Optional + +# 3rd party +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.plugins import get_plugin_logger + +# package +from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME + +# conditional +try: + from material import __version__ as material_version + from material.plugins.blog.plugin import BlogPlugin + from pymdownx.slugs import slugify + +except ImportError: + material_version = None + + +# ############################################################################ +# ########## Globals ############# +# ################################ + +logger = get_plugin_logger(MKDOCS_LOGGER_NAME) + +# ############################################################################ +# ########## Logic ############### +# ################################ + + +class IntegrationMaterialThemeBase: + # attributes + IS_THEME_MATERIAL: bool = False + IS_INSIDERS: bool = False + + def __init__(self, mkdocs_config: MkDocsConfig) -> None: + """Integration instantiation. + + Args: + mkdocs_config (MkDocsConfig): Mkdocs website configuration object. + """ + # store Mkdocs config as attribute + self.mkdocs_config = mkdocs_config + + self.IS_THEME_MATERIAL = self.is_mkdocs_theme_material() + self.IS_INSIDERS = self.is_mkdocs_theme_material_insiders() + + def is_mkdocs_theme_material( + self, mkdocs_config: Optional[MkDocsConfig] = None + ) -> bool: + """Check if the theme set in mkdocs.yml is material or not. + + Args: + mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object. + + Returns: + bool: True if the theme's name is 'material'. False if not. + """ + if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig): + mkdocs_config: MkDocsConfig = self.mkdocs_config + + self.IS_THEME_MATERIAL = mkdocs_config.theme.name == "material" + return self.IS_THEME_MATERIAL + + def is_mkdocs_theme_material_insiders(self) -> Optional[bool]: + """Check if the material theme is community or insiders edition. + + Returns: + bool: True if the theme is Insiders edition. False if community. None if + the Material theme is not installed. + """ + if not self.IS_THEME_MATERIAL: + return None + + if material_version is not None and "insiders" in material_version: + logger.debug("Material theme edition INSIDERS") + self.IS_INSIDERS = True + return True + else: + logger.debug("Material theme edition COMMUNITY") + self.IS_INSIDERS = False + return False diff --git a/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py b/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py new file mode 100644 index 0000000..b90c00c --- /dev/null +++ b/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py @@ -0,0 +1,103 @@ +#! python3 # noqa: E265 + +# ############################################################################ +# ########## Libraries ############# +# ################################## + +# standard library +from typing import Optional + +# 3rd party +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.plugins import get_plugin_logger + +# package +from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME +from mkdocs_rss_plugin.integrations.theme_material_base import ( + IntegrationMaterialThemeBase, +) + +# conditional +try: + from material import __version__ as material_version + from material.plugins.blog.plugin import BlogPlugin + +except ImportError: + material_version = None + + +# ############################################################################ +# ########## Globals ############# +# ################################ + +logger = get_plugin_logger(MKDOCS_LOGGER_NAME) + +# ############################################################################ +# ########## Logic ############### +# ################################ + + +class IntegrationMaterialBlog(IntegrationMaterialThemeBase): + # attributes + IS_ENABLED: bool = True + IS_BLOG_PLUGIN_ENABLED: bool = True + + def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> None: + """Integration instantiation. + + Args: + mkdocs_config (MkDocsConfig): Mkdocs website configuration object. + switch_force (bool, optional): option to force integration disabling. Set + it to False to disable it even if Social Cards are enabled in Mkdocs + configuration. Defaults to True. + """ + # check if the integration can be enabled or not + self.IS_BLOG_PLUGIN_ENABLED = self.is_blog_plugin_enabled_mkdocs( + mkdocs_config=mkdocs_config + ) + # if every conditions are True, enable the integration + self.IS_ENABLED = all([self.IS_THEME_MATERIAL, self.IS_BLOG_PLUGIN_ENABLED]) + + # except if the end-user wants to disable it + if switch_force is False: + self.IS_ENABLED = False + logger.debug( + "Integration with Blog (Material theme) is " + "disabled in plugin's option in Mkdocs configuration." + ) + + def is_blog_plugin_enabled_mkdocs( + self, mkdocs_config: Optional[MkDocsConfig] + ) -> bool: + """Check if blog plugin is installed and enabled. + + Args: + mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object. + + Returns: + bool: True if the theme material and the plugin blog is enabled. + """ + if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig): + mkdocs_config = self.mkdocs_config + + if not self.is_mkdocs_theme_material(mkdocs_config=mkdocs_config): + logger.debug("Installed theme is not 'material'. Integration disabled.") + return False + + if not mkdocs_config.plugins.get("material/blog"): + logger.debug("Material blog plugin is not listed in configuration.") + self.IS_BLOG_PLUGIN_ENABLED = False + return False + + self.blog_plugin_cfg: BlogPlugin | None = mkdocs_config.plugins.get( + "material/blog" + ) + + if not self.blog_plugin_cfg.config.enabled: + logger.debug("Material blog plugin is installed but disabled.") + self.IS_BLOG_PLUGIN_ENABLED = False + return False + + logger.debug("Material blog plugin is enabled in Mkdocs configuration.") + self.IS_BLOG_PLUGIN_ENABLED = True + return True diff --git a/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py b/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py index 1382d03..257efeb 100644 --- a/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py +++ b/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py @@ -17,11 +17,16 @@ # package from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME +from mkdocs_rss_plugin.integrations.theme_material_base import ( + IntegrationMaterialThemeBase, +) +from mkdocs_rss_plugin.integrations.theme_material_blog_plugin import ( + IntegrationMaterialBlog, +) # conditional try: from material import __version__ as material_version - from material.plugins.blog.plugin import BlogPlugin from pymdownx.slugs import slugify except ImportError: @@ -39,14 +44,11 @@ # ################################ -class IntegrationMaterialSocialCards: +class IntegrationMaterialSocialCards(IntegrationMaterialThemeBase): # attributes IS_ENABLED: bool = True - IS_BLOG_PLUGIN_ENABLED: bool = True IS_SOCIAL_PLUGIN_ENABLED: bool = True IS_SOCIAL_PLUGIN_CARDS_ENABLED: bool = True - IS_THEME_MATERIAL: bool = False - IS_INSIDERS: bool = False CARDS_MANIFEST: Optional[dict] = None def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> None: @@ -58,7 +60,10 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No it to False to disable it even if Social Cards are enabled in Mkdocs configuration. Defaults to True. """ - self.mkdocs_config = mkdocs_config + # support cards for blog posts + self.integration_material_blog = IntegrationMaterialBlog( + mkdocs_config=mkdocs_config + ) # check if the integration can be enabled or not self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = ( self.is_social_plugin_and_cards_enabled_mkdocs(mkdocs_config=mkdocs_config) @@ -91,9 +96,7 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No self.social_cards_cache_dir = self.get_social_cards_cache_dir( mkdocs_config=mkdocs_config ) - self.IS_BLOG_PLUGIN_ENABLED = self.is_blog_plugin_enabled_mkdocs( - mkdocs_config=mkdocs_config - ) + if self.is_mkdocs_theme_material_insiders(): self.load_cache_cards_manifest() @@ -101,77 +104,20 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No self.site_name = mkdocs_config.site_name self.site_description = mkdocs_config.site_description or "" - def is_mkdocs_theme_material(self, mkdocs_config: MkDocsConfig) -> bool: - """Check if the theme set in mkdocs.yml is material or not. - - Args: - mkdocs_config (MkDocsConfig): Mkdocs website configuration object. - - Returns: - bool: True if the theme's name is 'material'. False if not. - """ - self.IS_THEME_MATERIAL = mkdocs_config.theme.name == "material" - return self.IS_THEME_MATERIAL - - def is_mkdocs_theme_material_insiders(self) -> Optional[bool]: - """Check if the material theme is community or insiders edition. - - Returns: - bool: True if the theme is Insiders edition. False if community. None if - the Material theme is not installed. - """ - if not self.IS_THEME_MATERIAL: - return None - - if material_version is not None and "insiders" in material_version: - logger.debug("Material theme edition INSIDERS") - self.IS_INSIDERS = True - return True - else: - logger.debug("Material theme edition COMMUNITY") - self.IS_INSIDERS = False - return False - - def is_blog_plugin_enabled_mkdocs(self, mkdocs_config: MkDocsConfig) -> bool: - """Check if blog plugin is installed and enabled. - - Args: - mkdocs_config (MkDocsConfig): Mkdocs website configuration object. - - Returns: - bool: True if the theme material and the plugin blog is enabled. - """ - if not self.is_mkdocs_theme_material(mkdocs_config=mkdocs_config): - logger.debug("Installed theme is not 'material'. Integration disabled.") - return False - - if not mkdocs_config.plugins.get("material/blog"): - logger.debug("Material blog plugin is not listed in configuration.") - self.IS_BLOG_PLUGIN_ENABLED = False - return False - - self.blog_plugin_cfg: BlogPlugin | None = mkdocs_config.plugins.get( - "material/blog" - ) - - if not self.blog_plugin_cfg.config.enabled: - logger.debug("Material blog plugin is installed but disabled.") - self.IS_BLOG_PLUGIN_ENABLED = False - return False - - logger.debug("Material blog plugin is enabled in Mkdocs configuration.") - self.IS_BLOG_PLUGIN_ENABLED = True - return True - - def is_social_plugin_enabled_mkdocs(self, mkdocs_config: MkDocsConfig) -> bool: + def is_social_plugin_enabled_mkdocs( + self, mkdocs_config: Optional[MkDocsConfig] = None + ) -> bool: """Check if social plugin is installed and enabled. Args: - mkdocs_config (MkDocsConfig): Mkdocs website configuration object. + mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object. Returns: bool: True if the theme material and the plugin social cards is enabled. """ + if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig): + mkdocs_config = self.mkdocs_config + if not self.is_mkdocs_theme_material(mkdocs_config=mkdocs_config): logger.debug("Installed theme is not 'material'. Integration disabled.") return False @@ -315,9 +261,11 @@ def get_social_card_build_path_for_page( mkdocs_site_dir = self.mkdocs_site_build_dir # if page is a blog post - if self.IS_BLOG_PLUGIN_ENABLED and Path( + if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path( mkdocs_page.file.src_uri - ).is_relative_to(self.blog_plugin_cfg.config.blog_dir): + ).is_relative_to( + self.integration_material_blog.blog_plugin_cfg.config.blog_dir + ): expected_built_card_path = Path( f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/" f"{Path(mkdocs_page.file.dest_uri).parent}.png" @@ -358,9 +306,11 @@ def get_social_card_cache_path_for_page(self, mkdocs_page: Page) -> Optional[Pat if self.IS_INSIDERS: # if page is a blog post - if self.IS_BLOG_PLUGIN_ENABLED and Path( + if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path( mkdocs_page.file.src_uri - ).is_relative_to(self.blog_plugin_cfg.config.blog_dir): + ).is_relative_to( + self.integration_material_blog.blog_plugin_cfg.config.blog_dir + ): expected_cached_card_path = self.social_cards_cache_dir.joinpath( f"assets/images/social/{Path(mkdocs_page.file.dest_uri).parent}.png" ) @@ -426,9 +376,11 @@ def get_social_card_url_for_page( mkdocs_site_url = self.mkdocs_site_url # if page is a blog post - if self.IS_BLOG_PLUGIN_ENABLED and Path( + if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path( mkdocs_page.file.src_uri - ).is_relative_to(self.blog_plugin_cfg.config.blog_dir): + ).is_relative_to( + self.integration_material_blog.blog_plugin_cfg.config.blog_dir + ): page_social_card = ( f"{mkdocs_site_url}assets/images/social/" f"{Path(mkdocs_page.file.dest_uri).parent}.png" diff --git a/tests/fixtures/mkdocs_items_material_blog_enabled.yml b/tests/fixtures/mkdocs_items_material_blog_enabled.yml new file mode 100644 index 0000000..d637ea6 --- /dev/null +++ b/tests/fixtures/mkdocs_items_material_blog_enabled.yml @@ -0,0 +1,11 @@ +site_name: Test RSS Plugin +site_description: Test RSS with blog plugin also enabled +site_url: https://guts.github.io/mkdocs-rss-plugin + +plugins: + - blog: + blog_dir: blog + - rss + +theme: + name: material diff --git a/tests/test_integrations_material.py b/tests/test_integrations_material.py new file mode 100644 index 0000000..bb12b32 --- /dev/null +++ b/tests/test_integrations_material.py @@ -0,0 +1,71 @@ +#! python3 # noqa E265 + +"""Usage from the repo root folder: + +.. code-block:: python + + # for whole test + python -m unittest tests.test_build + +""" + +# ############################################################################# +# ########## Libraries ############# +# ################################## + +# Standard library +import unittest +from logging import DEBUG, getLogger +from pathlib import Path + +# 3rd party +from mkdocs.config import load_config + +# package +from mkdocs_rss_plugin.integrations.theme_material_base import ( + IntegrationMaterialThemeBase, +) + +# test suite +from tests.base import BaseTest + +# ############################################################################# +# ########## Classes ############### +# ################################## + +logger = getLogger(__name__) +logger.setLevel(DEBUG) + + +class TestRssPluginIntegrationsMaterialThem(BaseTest): + """Test integration of Material theme.""" + + # -- TESTS --------------------------------------------------------- + def test_plugin_config_theme_material(self): + # default reference + cfg_mkdocs = load_config( + str(Path("tests/fixtures/mkdocs_language_specific_material.yml").resolve()) + ) + + integration_social_cards = IntegrationMaterialThemeBase( + mkdocs_config=cfg_mkdocs + ) + self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) + + def test_plugin_config_theme_not_material(self): + # default reference + cfg_mkdocs = load_config( + str(Path("tests/fixtures/mkdocs_complete.yml").resolve()) + ) + + integration_social_cards = IntegrationMaterialThemeBase( + mkdocs_config=cfg_mkdocs + ) + self.assertFalse(integration_social_cards.IS_THEME_MATERIAL) + + +# ############################################################################## +# ##### Stand alone program ######## +# ################################## +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_integrations_material_blog.py b/tests/test_integrations_material_blog.py new file mode 100644 index 0000000..3255777 --- /dev/null +++ b/tests/test_integrations_material_blog.py @@ -0,0 +1,60 @@ +#! python3 # noqa E265 + +"""Usage from the repo root folder: + +.. code-block:: python + + # for whole test + python -m unittest tests.test_build + +""" + +# ############################################################################# +# ########## Libraries ############# +# ################################## + +# Standard library +import unittest +from logging import DEBUG, getLogger +from pathlib import Path + +# 3rd party +from mkdocs.config import load_config + +# package +from mkdocs_rss_plugin.integrations.theme_material_blog_plugin import ( + IntegrationMaterialBlog, +) + +# test suite +from tests.base import BaseTest + +# ############################################################################# +# ########## Classes ############### +# ################################## + +logger = getLogger(__name__) +logger.setLevel(DEBUG) + + +class TestRssPluginIntegrationsMaterialBlog(BaseTest): + """Test integration of Material Blog plugin with RSS plugin.""" + + # -- TESTS --------------------------------------------------------- + def test_plugin_config_blog_enabled(self): + # default reference + cfg_mkdocs = load_config( + str(Path("tests/fixtures/mkdocs_items_material_blog_enabled.yml").resolve()) + ) + + integration_social_cards = IntegrationMaterialBlog(mkdocs_config=cfg_mkdocs) + self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) + self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED) + self.assertTrue(integration_social_cards.IS_ENABLED) + + +# ############################################################################## +# ##### Stand alone program ######## +# ################################## +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_integrations_material_social_cards.py b/tests/test_integrations_material_social_cards.py index 64d4782..eab4e1c 100644 --- a/tests/test_integrations_material_social_cards.py +++ b/tests/test_integrations_material_social_cards.py @@ -131,7 +131,9 @@ def test_plugin_config_social_cards_enabled_with_blog_plugin(self): self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) - self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED) + self.assertTrue( + integration_social_cards.integration_material_blog.IS_BLOG_PLUGIN_ENABLED + ) self.assertTrue(integration_social_cards.IS_ENABLED) def test_simple_build(self):