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
@@ -39,6 +41,7 @@ class IntegrationMaterialSocialCards:
39
41
IS_SOCIAL_PLUGIN_CARDS_ENABLED : bool = True
40
42
IS_THEME_MATERIAL : bool = False
41
43
IS_INSIDERS : bool = False
44
+ CARDS_MANIFEST : dict | None = None
42
45
43
46
def __init__ (self , mkdocs_config : MkDocsConfig , switch_force : bool = True ) -> None :
44
47
"""Integration instanciation.
@@ -62,7 +65,6 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No
62
65
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED ,
63
66
]
64
67
)
65
- self .IS_INSIDERS = self .is_mkdocs_theme_material_insiders ()
66
68
67
69
# except if the end-user wants to disable it
68
70
if switch_force is False :
@@ -82,6 +84,12 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No
82
84
self .social_cards_cache_dir = self .get_social_cards_cache_dir (
83
85
mkdocs_config = mkdocs_config
84
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 ""
85
93
86
94
def is_mkdocs_theme_material (self , mkdocs_config : MkDocsConfig ) -> bool :
87
95
"""Check if the theme set in mkdocs.yml is material or not.
@@ -107,9 +115,11 @@ def is_mkdocs_theme_material_insiders(self) -> bool | None:
107
115
108
116
if material_version is not None and "insiders" in material_version :
109
117
logger .debug ("Material theme edition INSIDERS" )
118
+ self .IS_INSIDERS = True
110
119
return True
111
120
else :
112
121
logger .debug ("Material theme edition COMMUNITY" )
122
+ self .IS_INSIDERS = False
113
123
return False
114
124
115
125
def is_social_plugin_enabled_mkdocs (self , mkdocs_config : MkDocsConfig ) -> bool :
@@ -126,17 +136,17 @@ def is_social_plugin_enabled_mkdocs(self, mkdocs_config: MkDocsConfig) -> bool:
126
136
return False
127
137
128
138
if not mkdocs_config .plugins .get ("material/social" ):
129
- logger .debug ("Social plugin not listed in configuration." )
139
+ logger .debug ("Material Social plugin not listed in configuration." )
130
140
return False
131
141
132
142
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
133
143
134
144
if not social_plugin_cfg .config .enabled :
135
- logger .debug ("Social plugin is installed but disabled." )
145
+ logger .debug ("Material Social plugin is installed but disabled." )
136
146
self .IS_SOCIAL_PLUGIN_ENABLED = False
137
147
return False
138
148
139
- logger .debug ("Social plugin is enabled in Mkdocs configuration." )
149
+ logger .debug ("Material Social plugin is enabled in Mkdocs configuration." )
140
150
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
141
151
return True
142
152
@@ -157,19 +167,21 @@ def is_social_plugin_and_cards_enabled_mkdocs(
157
167
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
158
168
159
169
if not social_plugin_cfg .config .cards :
160
- 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
+ )
161
173
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED = False
162
174
return False
163
175
164
- logger .debug ("Social cards are enabled in Mkdocs configuration." )
176
+ logger .debug ("Material Social cards are enabled in Mkdocs configuration." )
165
177
self .IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
166
178
return True
167
179
168
180
def is_social_plugin_enabled_page (
169
181
self , mkdocs_page : Page , fallback_value : bool = True
170
182
) -> bool :
171
183
"""Check if the social plugin is enabled or disabled for a specific page. Plugin
172
- has to enabled in Mkdocs configuration before.
184
+ has to be enabled in Mkdocs configuration before.
173
185
174
186
Args:
175
187
mkdocs_page (Page): Mkdocs page object.
@@ -183,7 +195,31 @@ def is_social_plugin_enabled_page(
183
195
"cards" , fallback_value
184
196
)
185
197
186
- def get_social_cards_build_dir (self , mkdocs_config : MkDocsConfig ) -> 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 :
187
223
"""Get Social Cards folder within Mkdocs site_dir.
188
224
See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir
189
225
@@ -196,13 +232,13 @@ def get_social_cards_build_dir(self, mkdocs_config: MkDocsConfig) -> str:
196
232
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
197
233
198
234
logger .debug (
199
- "Social cards folder in Mkdocs build directory: "
235
+ "Material Social cards folder in Mkdocs build directory: "
200
236
f"{ social_plugin_cfg .config .cards_dir } ."
201
237
)
202
238
203
- return social_plugin_cfg .config .cards_dir
239
+ return Path ( social_plugin_cfg .config .cards_dir ). resolve ()
204
240
205
- def get_social_cards_cache_dir (self , mkdocs_config : MkDocsConfig ) -> str :
241
+ def get_social_cards_cache_dir (self , mkdocs_config : MkDocsConfig ) -> Path :
206
242
"""Get Social Cards folder within Mkdocs site_dir.
207
243
See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir
208
244
@@ -213,16 +249,17 @@ def get_social_cards_cache_dir(self, mkdocs_config: MkDocsConfig) -> str:
213
249
str: True if the theme material and the plugin social cards is enabled.
214
250
"""
215
251
social_plugin_cfg = mkdocs_config .plugins .get ("material/social" )
252
+ self .social_cards_cache_dir = Path (social_plugin_cfg .config .cache_dir ).resolve ()
216
253
217
254
logger .debug (
218
- "Social cards cache folder: " f"{ social_plugin_cfg . config . cache_dir } ."
255
+ "Material Social cards cache folder: " f"{ self . social_cards_cache_dir } ."
219
256
)
220
257
221
- return social_plugin_cfg . config . cache_dir
258
+ return self . social_cards_cache_dir
222
259
223
260
def get_social_card_build_path_for_page (
224
261
self , mkdocs_page : Page , mkdocs_site_dir : str | None = None
225
- ) -> Path :
262
+ ) -> Path | None :
226
263
"""Get social card path in Mkdocs build dir for a specific page.
227
264
228
265
Args:
@@ -231,31 +268,80 @@ def get_social_card_build_path_for_page(
231
268
'class.mkdocs_site_build_dir' is used. is Defaults to None.
232
269
233
270
Returns:
234
- str : path to the image once published
271
+ Path : path to the image once published
235
272
"""
236
273
if mkdocs_site_dir is None and self .mkdocs_site_build_dir :
237
274
mkdocs_site_dir = self .mkdocs_site_build_dir
238
275
239
- return Path (
276
+ expected_built_card_path = Path (
240
277
f"{ mkdocs_site_dir } /{ self .social_cards_assets_dir } /"
241
278
f"{ Path (mkdocs_page .file .src_uri ).with_suffix ('.png' )} "
242
279
)
243
280
244
- def get_social_card_cache_path_for_page (self , mkdocs_page : Page ) -> Path :
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 :
245
291
"""Get social card path in social plugin cache folder for a specific page.
246
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
+
247
300
Args:
248
301
mkdocs_page (Page): Mkdocs page object.
249
- social_plugin_cache_dir (Optional[str], optional): Mkdocs build site dir. If None, the
250
- 'class.mkdocs_site_build_dir' is used. is Defaults to None.
251
302
252
303
Returns:
253
- str : path to the image once published
304
+ Path : path to the image in local cache folder if it exists
254
305
"""
255
- return Path (
256
- f"{ self .social_cards_assets_dir } /"
257
- f"{ Path (mkdocs_page .file .src_uri ).with_suffix ('.png' )} "
258
- )
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
259
345
260
346
def get_social_card_url_for_page (
261
347
self ,
0 commit comments