1""" 2 sphinx.util.fileutil 3 ~~~~~~~~~~~~~~~~~~~~ 4 5 File utility functions for Sphinx. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11import os 12import posixpath 13from typing import Callable, Dict 14 15from docutils.utils import relative_path 16 17from sphinx.util.osutil import copyfile, ensuredir 18from sphinx.util.typing import PathMatcher 19 20if False: 21 # For type annotation 22 from sphinx.util.template import BaseRenderer 23 24 25def copy_asset_file(source: str, destination: str, 26 context: Dict = None, renderer: "BaseRenderer" = None) -> None: 27 """Copy an asset file to destination. 28 29 On copying, it expands the template variables if context argument is given and 30 the asset is a template file. 31 32 :param source: The path to source file 33 :param destination: The path to destination file or directory 34 :param context: The template variables. If not given, template files are simply copied 35 :param renderer: The template engine. If not given, SphinxRenderer is used by default 36 """ 37 if not os.path.exists(source): 38 return 39 40 if os.path.isdir(destination): 41 # Use source filename if destination points a directory 42 destination = os.path.join(destination, os.path.basename(source)) 43 44 if source.lower().endswith('_t') and context is not None: 45 if renderer is None: 46 from sphinx.util.template import SphinxRenderer 47 renderer = SphinxRenderer() 48 49 with open(source, encoding='utf-8') as fsrc: 50 if destination.lower().endswith('_t'): 51 destination = destination[:-2] 52 with open(destination, 'w', encoding='utf-8') as fdst: 53 fdst.write(renderer.render_string(fsrc.read(), context)) 54 else: 55 copyfile(source, destination) 56 57 58def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False, 59 context: Dict = None, renderer: "BaseRenderer" = None, 60 onerror: Callable[[str, Exception], None] = None) -> None: 61 """Copy asset files to destination recursively. 62 63 On copying, it expands the template variables if context argument is given and 64 the asset is a template file. 65 66 :param source: The path to source file or directory 67 :param destination: The path to destination directory 68 :param excluded: The matcher to determine the given path should be copied or not 69 :param context: The template variables. If not given, template files are simply copied 70 :param renderer: The template engine. If not given, SphinxRenderer is used by default 71 :param onerror: The error handler. 72 """ 73 if not os.path.exists(source): 74 return 75 76 if renderer is None: 77 from sphinx.util.template import SphinxRenderer 78 renderer = SphinxRenderer() 79 80 ensuredir(destination) 81 if os.path.isfile(source): 82 copy_asset_file(source, destination, context, renderer) 83 return 84 85 for root, dirs, files in os.walk(source, followlinks=True): 86 reldir = relative_path(source, root) 87 for dir in dirs[:]: 88 if excluded(posixpath.join(reldir, dir)): 89 dirs.remove(dir) 90 else: 91 ensuredir(posixpath.join(destination, reldir, dir)) 92 93 for filename in files: 94 if not excluded(posixpath.join(reldir, filename)): 95 try: 96 copy_asset_file(posixpath.join(root, filename), 97 posixpath.join(destination, reldir), 98 context, renderer) 99 except Exception as exc: 100 if onerror: 101 onerror(posixpath.join(root, filename), exc) 102 else: 103 raise 104