88import re
99import sys
1010import warnings
11+ import zlib
1112from datetime import datetime
1213from os import path
1314from typing import IO , Any , Iterable , Iterator , List , Tuple , Type
@@ -649,6 +650,12 @@ def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, A
649650 'page_source_suffix' : source_suffix ,
650651 }
651652
653+ def copy_assets (self ) -> None :
654+ self .finish_tasks .add_task (self .copy_download_files )
655+ self .finish_tasks .add_task (self .copy_static_files )
656+ self .finish_tasks .add_task (self .copy_extra_files )
657+ self .finish_tasks .join ()
658+
652659 def write_doc (self , docname : str , doctree : nodes .document ) -> None :
653660 destination = StringOutput (encoding = 'utf-8' )
654661 doctree .settings = self .docsettings
@@ -678,9 +685,6 @@ def finish(self) -> None:
678685 self .finish_tasks .add_task (self .gen_pages_from_extensions )
679686 self .finish_tasks .add_task (self .gen_additional_pages )
680687 self .finish_tasks .add_task (self .copy_image_files )
681- self .finish_tasks .add_task (self .copy_download_files )
682- self .finish_tasks .add_task (self .copy_static_files )
683- self .finish_tasks .add_task (self .copy_extra_files )
684688 self .finish_tasks .add_task (self .write_buildinfo )
685689
686690 # dump the search index
@@ -1193,8 +1197,11 @@ def css_tag(css: Stylesheet) -> str:
11931197 value = css .attributes [key ]
11941198 if value is not None :
11951199 attrs .append (f'{ key } ="{ html .escape (value , True )} "' )
1196- attrs .append ('href="%s"' % pathto (css .filename , resource = True ))
1197- return '<link %s />' % ' ' .join (attrs )
1200+ uri = pathto (css .filename , resource = True )
1201+ if checksum := _file_checksum (app .outdir , css .filename ):
1202+ uri += f'?v={ checksum } '
1203+ attrs .append (f'href="{ uri } "' )
1204+ return f'<link { " " .join (attrs )} />'
11981205
11991206 context ['css_tag' ] = css_tag
12001207
@@ -1217,14 +1224,17 @@ def js_tag(js: JavaScript) -> str:
12171224 if key == 'body' :
12181225 body = value
12191226 elif key == 'data_url_root' :
1220- attrs .append ('data-url_root="%s"' % pathto ('' , resource = True ))
1227+ attrs .append (f 'data-url_root="{ pathto ("" , resource = True )} "' )
12211228 else :
12221229 attrs .append (f'{ key } ="{ html .escape (value , True )} "' )
12231230 if js .filename :
1224- attrs .append ('src="%s"' % pathto (js .filename , resource = True ))
1231+ uri = pathto (js .filename , resource = True )
1232+ if checksum := _file_checksum (app .outdir , js .filename ):
1233+ uri += f'?v={ checksum } '
1234+ attrs .append (f'src="{ uri } "' )
12251235 else :
12261236 # str value (old styled)
1227- attrs .append ('src="%s"' % pathto (js , resource = True ))
1237+ attrs .append (f 'src="{ pathto (js , resource = True )} "' )
12281238
12291239 if attrs :
12301240 return f'<script { " " .join (attrs )} >{ body } </script>'
@@ -1234,6 +1244,21 @@ def js_tag(js: JavaScript) -> str:
12341244 context ['js_tag' ] = js_tag
12351245
12361246
1247+ def _file_checksum (outdir : str , filename : str ) -> str :
1248+ # Don't generate checksums for HTTP URIs
1249+ if '://' in filename :
1250+ return ''
1251+ try :
1252+ # Ensure universal newline mode is used to avoid checksum differences
1253+ with open (path .join (outdir , filename ), encoding = 'utf-8' ) as f :
1254+ content = f .read ().encode (encoding = 'utf-8' )
1255+ except FileNotFoundError :
1256+ return ''
1257+ if not content :
1258+ return ''
1259+ return f'{ zlib .crc32 (content ):08x} '
1260+
1261+
12371262def setup_resource_paths (app : Sphinx , pagename : str , templatename : str ,
12381263 context : dict , doctree : Node ) -> None :
12391264 """Set up relative resource paths."""
0 commit comments