66import re
77from hashlib import sha1
88from math import ceil
9+ from pathlib import Path
910from typing import TYPE_CHECKING , Any
1011
1112from docutils import nodes
1213
1314from sphinx .locale import __
1415from sphinx .transforms import SphinxTransform
1516from sphinx .util import logging , requests
17+ from sphinx .util ._pathlib import _StrPath
1618from sphinx .util .http_date import epoch_to_rfc1123 , rfc1123_to_epoch
1719from sphinx .util .images import get_image_extension , guess_mimetype , parse_data_uri
1820from sphinx .util .osutil import ensuredir
@@ -65,50 +67,58 @@ def handle(self, node: nodes.image) -> None:
6567 basename = CRITICAL_PATH_CHAR_RE .sub ("_" , basename )
6668
6769 uri_hash = sha1 (node ['uri' ].encode (), usedforsecurity = False ).hexdigest ()
68- ensuredir (os .path .join (self .imagedir , uri_hash ))
69- path = os .path .join (self .imagedir , uri_hash , basename )
70-
71- headers = {}
72- if os .path .exists (path ):
73- timestamp : float = ceil (os .stat (path ).st_mtime )
74- headers ['If-Modified-Since' ] = epoch_to_rfc1123 (timestamp )
75-
76- config = self .app .config
77- r = requests .get (
78- node ['uri' ], headers = headers ,
79- _user_agent = config .user_agent ,
80- _tls_info = (config .tls_verify , config .tls_cacerts ),
81- )
82- if r .status_code >= 400 :
83- logger .warning (__ ('Could not fetch remote image: %s [%d]' ),
84- node ['uri' ], r .status_code )
85- else :
86- self .app .env .original_image_uri [path ] = node ['uri' ]
87-
88- if r .status_code == 200 :
89- with open (path , 'wb' ) as f :
90- f .write (r .content )
91-
92- last_modified = r .headers .get ('last-modified' )
93- if last_modified :
94- timestamp = rfc1123_to_epoch (last_modified )
95- os .utime (path , (timestamp , timestamp ))
96-
97- mimetype = guess_mimetype (path , default = '*' )
98- if mimetype != '*' and os .path .splitext (basename )[1 ] == '' :
99- # append a suffix if URI does not contain suffix
100- ext = get_image_extension (mimetype )
101- newpath = os .path .join (self .imagedir , uri_hash , basename + ext )
102- os .replace (path , newpath )
103- self .app .env .original_image_uri .pop (path )
104- self .app .env .original_image_uri [newpath ] = node ['uri' ]
105- path = newpath
106- node ['candidates' ].pop ('?' )
107- node ['candidates' ][mimetype ] = path
108- node ['uri' ] = path
109- self .app .env .images .add_file (self .env .docname , path )
70+ path = Path (self .imagedir , uri_hash , basename )
71+ path .parent .mkdir (parents = True , exist_ok = True )
72+ self ._download_image (node , path )
73+
11074 except Exception as exc :
111- logger .warning (__ ('Could not fetch remote image: %s [%s]' ), node ['uri' ], exc )
75+ msg = __ ('Could not fetch remote image: %s [%s]' )
76+ logger .warning (msg , node ['uri' ], exc )
77+
78+ def _download_image (self , node : nodes .image , path : Path ) -> None :
79+ headers = {}
80+ if path .exists ():
81+ timestamp : float = ceil (path .stat ().st_mtime )
82+ headers ['If-Modified-Since' ] = epoch_to_rfc1123 (timestamp )
83+
84+ config = self .app .config
85+ r = requests .get (
86+ node ['uri' ], headers = headers ,
87+ _user_agent = config .user_agent ,
88+ _tls_info = (config .tls_verify , config .tls_cacerts ),
89+ )
90+ if r .status_code >= 400 :
91+ msg = __ ('Could not fetch remote image: %s [%d]' )
92+ logger .warning (msg , node ['uri' ], r .status_code )
93+ else :
94+ self .app .env .original_image_uri [_StrPath (path )] = node ['uri' ]
95+
96+ if r .status_code == 200 :
97+ path .write_bytes (r .content )
98+ if last_modified := r .headers .get ('Last-Modified' ):
99+ timestamp = rfc1123_to_epoch (last_modified )
100+ os .utime (path , (timestamp , timestamp ))
101+
102+ self ._process_image (node , path )
103+
104+ def _process_image (self , node : nodes .image , path : Path ) -> None :
105+ str_path = _StrPath (path )
106+ self .app .env .original_image_uri [str_path ] = node ['uri' ]
107+
108+ mimetype = guess_mimetype (path , default = '*' )
109+ if mimetype != '*' and path .suffix == '' :
110+ # append a suffix if URI does not contain suffix
111+ ext = get_image_extension (mimetype ) or ''
112+ with_ext = path .with_name (path .name + ext )
113+ os .replace (path , with_ext )
114+ self .app .env .original_image_uri .pop (str_path )
115+ self .app .env .original_image_uri [_StrPath (with_ext )] = node ['uri' ]
116+ path = with_ext
117+ path_str = str (path )
118+ node ['candidates' ].pop ('?' )
119+ node ['candidates' ][mimetype ] = path_str
120+ node ['uri' ] = path_str
121+ self .app .env .images .add_file (self .env .docname , path_str )
112122
113123
114124class DataURIExtractor (BaseImageConverter ):
@@ -130,16 +140,17 @@ def handle(self, node: nodes.image) -> None:
130140
131141 ensuredir (os .path .join (self .imagedir , 'embeded' ))
132142 digest = sha1 (image .data , usedforsecurity = False ).hexdigest ()
133- path = os . path . join (self .imagedir , 'embeded' , digest + ext )
143+ path = _StrPath (self .imagedir , 'embeded' , digest + ext )
134144 self .app .env .original_image_uri [path ] = node ['uri' ]
135145
136146 with open (path , 'wb' ) as f :
137147 f .write (image .data )
138148
149+ path_str = str (path )
139150 node ['candidates' ].pop ('?' )
140- node ['candidates' ][image .mimetype ] = path
141- node ['uri' ] = path
142- self .app .env .images .add_file (self .env .docname , path )
151+ node ['candidates' ][image .mimetype ] = path_str
152+ node ['uri' ] = path_str
153+ self .app .env .images .add_file (self .env .docname , path_str )
143154
144155
145156def get_filename_for (filename : str , mimetype : str ) -> str :
@@ -258,7 +269,7 @@ def handle(self, node: nodes.image) -> None:
258269 node ['candidates' ][_to ] = destpath
259270 node ['uri' ] = destpath
260271
261- self .env .original_image_uri [destpath ] = srcpath
272+ self .env .original_image_uri [_StrPath ( destpath ) ] = srcpath
262273 self .env .images .add_file (self .env .docname , destpath )
263274
264275 def convert (self , _from : str , _to : str ) -> bool :
0 commit comments