5
5
# ##################################
6
6
7
7
# standard library
8
+ import json
9
+ from hashlib import md5
8
10
from pathlib import Path
9
11
10
12
# 3rd party
11
- from mkdocs .config .config_options import Config
13
+ from mkdocs .config .defaults import MkDocsConfig
12
14
from mkdocs .plugins import get_plugin_logger
13
15
from mkdocs .structure .pages import Page
14
16
15
17
# package
16
18
from mkdocs_rss_plugin .constants import MKDOCS_LOGGER_NAME
17
19
20
+ # conditional
21
+ try :
22
+ from material import __version__ as material_version
23
+ except ImportError :
24
+ material_version = None
25
+
18
26
# ############################################################################
19
27
# ########## Globals #############
20
28
# ################################
@@ -32,12 +40,14 @@ class IntegrationMaterialSocialCards:
32
40
IS_SOCIAL_PLUGIN_ENABLED : bool = True
33
41
IS_SOCIAL_PLUGIN_CARDS_ENABLED : bool = True
34
42
IS_THEME_MATERIAL : bool = False
43
+ IS_INSIDERS : bool = False
44
+ CARDS_MANIFEST : dict | None = None
35
45
36
- def __init__ (self , mkdocs_config : Config , switch_force : bool = True ) -> None :
46
+ def __init__ (self , mkdocs_config : MkDocsConfig , switch_force : bool = True ) -> None :
37
47
"""Integration instantiation.
38
48
39
49
Args:
40
- mkdocs_config (Config ): Mkdocs website configuration object.
50
+ mkdocs_config (MkDocsConfig ): Mkdocs website configuration object.
41
51
switch_force (bool, optional): option to force integration disabling. Set
42
52
it to False to disable it even if Social Cards are enabled in Mkdocs
43
53
configuration. Defaults to True.
@@ -68,55 +78,85 @@ def __init__(self, mkdocs_config: Config, switch_force: bool = True) -> None:
68
78
if self .IS_ENABLED :
69
79
self .mkdocs_site_url = mkdocs_config .site_url
70
80
self .mkdocs_site_build_dir = mkdocs_config .site_dir
71
- self .social_cards_assets_dir = self .get_social_cards_dir (
81
+ self .social_cards_assets_dir = self .get_social_cards_build_dir (
82
+ mkdocs_config = mkdocs_config
83
+ )
84
+ self .social_cards_cache_dir = self .get_social_cards_cache_dir (
72
85
mkdocs_config = mkdocs_config
73
86
)
87
+ if self .is_mkdocs_theme_material_insiders ():
88
+ self .load_cache_cards_manifest ()
89
+
90
+ # store some attributes used to compute social card hash
91
+ self .site_name = mkdocs_config .site_name
92
+ self .site_description = mkdocs_config .site_description or ""
74
93
75
- def is_theme_material (self , mkdocs_config : Config ) -> bool :
94
+ def is_mkdocs_theme_material (self , mkdocs_config : MkDocsConfig ) -> bool :
76
95
"""Check if the theme set in mkdocs.yml is material or not.
77
96
78
97
Args:
79
- mkdocs_config (Config ): Mkdocs website configuration object.
98
+ mkdocs_config (MkDocsConfig ): Mkdocs website configuration object.
80
99
81
100
Returns:
82
101
bool: True if the theme's name is 'material'. False if not.
83
102
"""
84
103
self .IS_THEME_MATERIAL = mkdocs_config .theme .name == "material"
85
104
return self .IS_THEME_MATERIAL
86
105
87
- def is_social_plugin_enabled_mkdocs (self , mkdocs_config : Config ) -> bool :
106
+ def is_mkdocs_theme_material_insiders (self ) -> bool | None :
107
+ """Check if the material theme is community or insiders edition.
108
+
109
+ Returns:
110
+ bool: True if the theme is Insiders edition. False if community. None if
111
+ the Material theme is not installed.
112
+ """
113
+ if not self .IS_THEME_MATERIAL :
114
+ return None
115
+
116
+ if material_version is not None and "insiders" in material_version :
117
+ logger .debug ("Material theme edition INSIDERS" )
118
+ self .IS_INSIDERS = True
119
+ return True
120
+ else :
121
+ logger .debug ("Material theme edition COMMUNITY" )
122
+ self .IS_INSIDERS = False
123
+ return False
124
+
125
+ def is_social_plugin_enabled_mkdocs (self , mkdocs_config : MkDocsConfig ) -> bool :
88
126
"""Check if social plugin is installed and enabled.
89
127
90
128
Args:
91
- mkdocs_config (Config ): Mkdocs website configuration object.
129
+ mkdocs_config (MkDocsConfig ): Mkdocs website configuration object.
92
130
93
131
Returns:
94
132
bool: True if the theme material and the plugin social cards is enabled.
95
133
"""
96
- if not self .is_theme_material (mkdocs_config = mkdocs_config ):
134
+ if not self .is_mkdocs_theme_material (mkdocs_config = mkdocs_config ):
97
135
logger .debug ("Installed theme is not 'material'. Integration disabled." )
98
136
return False
99
137
100
138
if not mkdocs_config .plugins .get ("material/social" ):
101
- logger .debug ("Social plugin not listed in configuration." )
139
+ logger .debug ("Material Social plugin not listed in configuration." )
102
140
return False
103
141
104
142
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
105
143
106
144
if not social_plugin_cfg .config .enabled :
107
- logger .debug ("Social plugin is installed but disabled." )
145
+ logger .debug ("Material Social plugin is installed but disabled." )
108
146
self .IS_SOCIAL_PLUGIN_ENABLED = False
109
147
return False
110
148
111
- logger .debug ("Social plugin is enabled in Mkdocs configuration." )
149
+ logger .debug ("Material Social plugin is enabled in Mkdocs configuration." )
112
150
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
113
151
return True
114
152
115
- def is_social_plugin_and_cards_enabled_mkdocs (self , mkdocs_config : Config ) -> bool :
153
+ def is_social_plugin_and_cards_enabled_mkdocs (
154
+ self , mkdocs_config : MkDocsConfig
155
+ ) -> bool :
116
156
"""Check if social cards plugin is enabled.
117
157
118
158
Args:
119
- mkdocs_config (Config ): Mkdocs website configuration object.
159
+ mkdocs_config (MkDocsConfig ): Mkdocs website configuration object.
120
160
121
161
Returns:
122
162
bool: True if the theme material and the plugin social cards is enabled.
@@ -127,19 +167,21 @@ def is_social_plugin_and_cards_enabled_mkdocs(self, mkdocs_config: Config) -> bo
127
167
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
128
168
129
169
if not social_plugin_cfg .config .cards :
130
- logger .debug ("Social plugin is installed, present but cards are disabled." )
170
+ logger .debug (
171
+ "Material Social plugin is installed, present but cards are disabled."
172
+ )
131
173
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED = False
132
174
return False
133
175
134
- logger .debug ("Social cards are enabled in Mkdocs configuration." )
176
+ logger .debug ("Material Social cards are enabled in Mkdocs configuration." )
135
177
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
136
178
return True
137
179
138
180
def is_social_plugin_enabled_page (
139
181
self , mkdocs_page : Page , fallback_value : bool = True
140
182
) -> bool :
141
183
"""Check if the social plugin is enabled or disabled for a specific page. Plugin
142
- has to enabled in Mkdocs configuration before.
184
+ has to be enabled in Mkdocs configuration before.
143
185
144
186
Args:
145
187
mkdocs_page (Page): Mkdocs page object.
@@ -153,46 +195,154 @@ def is_social_plugin_enabled_page(
153
195
"cards" , fallback_value
154
196
)
155
197
156
- def get_social_cards_dir (self , mkdocs_config : Config ) -> str :
198
+ def load_cache_cards_manifest (self ) -> dict | None :
199
+ """Load social cards manifest if the file exists.
200
+
201
+ Returns:
202
+ dict | None: manifest as dict or None if the file does not exist
203
+ """
204
+ cache_cards_manifest = Path (self .social_cards_cache_dir ).joinpath (
205
+ "manifest.json"
206
+ )
207
+ if not cache_cards_manifest .is_file ():
208
+ logger .debug (
209
+ "Material Social Cards cache manifest file not found: "
210
+ f"{ cache_cards_manifest } "
211
+ )
212
+ return None
213
+
214
+ with cache_cards_manifest .open (mode = "r" , encoding = "UTF-8" ) as manifest :
215
+ self .CARDS_MANIFEST = json .load (manifest )
216
+ logger .debug (
217
+ f"Material Social Cards cache manifest loaded from { cache_cards_manifest } "
218
+ )
219
+
220
+ return self .CARDS_MANIFEST
221
+
222
+ def get_social_cards_build_dir (self , mkdocs_config : MkDocsConfig ) -> Path :
157
223
"""Get Social Cards folder within Mkdocs site_dir.
158
224
See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir
159
225
160
226
Args:
161
- mkdocs_config (Config ): Mkdocs website configuration object.
227
+ mkdocs_config (MkDocsConfig ): Mkdocs website configuration object.
162
228
163
229
Returns:
164
230
str: True if the theme material and the plugin social cards is enabled.
165
231
"""
166
232
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
167
233
168
234
logger .debug (
169
- "Social cards folder in Mkdocs build directory: "
235
+ "Material Social cards folder in Mkdocs build directory: "
170
236
f"{ social_plugin_cfg .config .cards_dir } ."
171
237
)
172
238
173
- return social_plugin_cfg .config .cards_dir
239
+ return Path (social_plugin_cfg .config .cards_dir ).resolve ()
240
+
241
+ def get_social_cards_cache_dir (self , mkdocs_config : MkDocsConfig ) -> Path :
242
+ """Get Social Cards folder within Mkdocs site_dir.
243
+ See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir
244
+
245
+ Args:
246
+ mkdocs_config (MkDocsConfig): Mkdocs website configuration object.
247
+
248
+ Returns:
249
+ str: True if the theme material and the plugin social cards is enabled.
250
+ """
251
+ social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
252
+ self .social_cards_cache_dir = Path (social_plugin_cfg .config .cache_dir ).resolve ()
253
+
254
+ logger .debug (
255
+ "Material Social cards cache folder: " f"{ self .social_cards_cache_dir } ."
256
+ )
257
+
258
+ return self .social_cards_cache_dir
174
259
175
260
def get_social_card_build_path_for_page (
176
261
self , mkdocs_page : Page , mkdocs_site_dir : str | None = None
177
- ) -> Path :
178
- """Get social card URL for a specific page in documentation .
262
+ ) -> Path | None :
263
+ """Get social card path in Mkdocs build dir for a specific page.
179
264
180
265
Args:
181
266
mkdocs_page (Page): Mkdocs page object.
182
267
mkdocs_site_dir (Optional[str], optional): Mkdocs build site dir. If None, the
183
268
'class.mkdocs_site_build_dir' is used. is Defaults to None.
184
269
185
270
Returns:
186
- str: URL to the image once published
271
+ Path: path to the image once published
187
272
"""
188
273
if mkdocs_site_dir is None and self .mkdocs_site_build_dir :
189
274
mkdocs_site_dir = self .mkdocs_site_build_dir
190
275
191
- return Path (
276
+ expected_built_card_path = Path (
192
277
f"{ mkdocs_site_dir } /{ self .social_cards_assets_dir } /"
193
278
f"{ Path (mkdocs_page .file .src_uri ).with_suffix ('.png' )} "
194
279
)
195
280
281
+ if expected_built_card_path .is_file ():
282
+ logger .debug (
283
+ f"Social card file found in cache folder: { expected_built_card_path } "
284
+ )
285
+ return expected_built_card_path
286
+ else :
287
+ logger .debug (f"Not found: { expected_built_card_path } " )
288
+ return None
289
+
290
+ def get_social_card_cache_path_for_page (self , mkdocs_page : Page ) -> Path | None :
291
+ """Get social card path in social plugin cache folder for a specific page.
292
+
293
+ Note:
294
+ As we write this code (June 2024), the cache mechanism in Insiders edition
295
+ has stores images directly with the corresponding Page's path and name and
296
+ keep a correspondance matrix with hashes in a manifest.json;
297
+ the cache mechanism in Community edition uses the hash as file names without
298
+ any exposed matching criteria.
299
+
300
+ Args:
301
+ mkdocs_page (Page): Mkdocs page object.
302
+
303
+ Returns:
304
+ Path: path to the image in local cache folder if it exists
305
+ """
306
+ if self .IS_INSIDERS :
307
+ expected_cached_card_path = self .social_cards_cache_dir .joinpath (
308
+ f"assets/images/social/{ Path (mkdocs_page .file .src_uri ).with_suffix ('.png' )} "
309
+ )
310
+ if expected_cached_card_path .is_file ():
311
+ logger .debug (
312
+ f"Social card file found in cache folder: { expected_cached_card_path } "
313
+ )
314
+ return expected_cached_card_path
315
+ else :
316
+ logger .debug (f"Not found: { expected_cached_card_path } " )
317
+
318
+ else :
319
+ if "description" in mkdocs_page .meta :
320
+ description = mkdocs_page .meta ["description" ]
321
+ else :
322
+ description = self .site_description
323
+
324
+ page_hash = md5 (
325
+ "" .join (
326
+ [
327
+ self .site_name ,
328
+ str (mkdocs_page .meta .get ("title" , mkdocs_page .title )),
329
+ description ,
330
+ ]
331
+ ).encode ("utf-8" )
332
+ )
333
+ expected_cached_card_path = self .social_cards_cache_dir .joinpath (
334
+ f"{ page_hash .hexdigest ()} .png"
335
+ )
336
+
337
+ if expected_cached_card_path .is_file ():
338
+ logger .debug (
339
+ f"Social card file found in cache folder: { expected_cached_card_path } "
340
+ )
341
+ return expected_cached_card_path
342
+ else :
343
+ logger .debug (f"Not found: { expected_cached_card_path } " )
344
+ return None
345
+
196
346
def get_social_card_url_for_page (
197
347
self ,
198
348
mkdocs_page : Page ,
0 commit comments