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