# Buggy code
def copy_asset(
    source: str,
    destination: str,
    excluded: PathMatcher = lambda path: False,
    context: Dict = None,
    renderer: "BaseRenderer" = None,
) -> None:
    """Copy asset files to destination recursively.

    On copying, it expands the template variables if context argument is given and
    the asset is a template file.

    :param source: The path to source file or directory
    :param destination: The path to destination directory
    :param excluded: The matcher to determine the given path should be copied or not
    :param context: The template variables.  If not given, template files are simply copied
    :param renderer: The template engine.  If not given, SphinxRenderer is used by default
    """
    if not os.path.exists(source):
        return

    if renderer is None:
        from sphinx.util.template import SphinxRenderer

        renderer = SphinxRenderer()

    ensuredir(destination)
    if os.path.isfile(source):
        copy_asset_file(source, destination, context, renderer)
        return

    for root, dirs, files in os.walk(source, followlinks=True):
        reldir = relative_path(source, root)
        for dir in dirs[:]:
            if excluded(posixpath.join(reldir, dir)):
                dirs.remove(dir)
            else:
                ensuredir(posixpath.join(destination, reldir, dir))

        for filename in files:
            if not excluded(posixpath.join(reldir, filename)):
                copy_asset_file(
                    posixpath.join(root, filename),
                    posixpath.join(destination, reldir),
                    context,
                    renderer,
                )


# Traceback (jinja2.exceptions.UndefinedError)
Exception occurred:
File "c:\users\riedgar\appdata\local\continuum\miniconda3\envs\py-36\lib\site-packages\jinja2\sandbox.py", line 360, in is_safe_callable
getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
jinja2.exceptions.UndefinedError: 'pathto' is undefined
The full traceback has been saved in C:\Users\riedgar\AppData\Local\Temp\sphinx-err-tnwtajjw.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!
Traceback (most recent call last):
File "C:\Users\riedgar\AppData\Local\Continuum\miniconda3\envs\py-36\Scripts\sphinx-multiversion-script.py", line 33, in <module>
sys.exit(load_entry_point('sphinx-multiversion', 'console_scripts', 'sphinx-multiversion')())
File "c:\users\riedgar\source\repos\sphinx-multiversion\sphinx_multiversion\main.py", line 302, in main
subprocess.check_call(cmd, cwd=current_cwd)
File "c:\users\riedgar\appdata\local\continuum\miniconda3\envs\py-36\lib\subprocess.py", line 311, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '('c:\\users\\riedgar\\appdata\\local\\continuum\\miniconda3\\envs\\py-36\\python.exe', '-m', 'sphinx', '-D', 'smv_metadata_path=C:\\Users\\riedgar\\AppData\\Local\\Temp\\tmp4vfkrz7o\\versions.json', '-D', 'smv_current_version=master', '-c', 'C:\\Users\\riedgar\\source\\repos\\fairlearn\\docs', 'C:\\Users\\riedgar\\AppData\\Local\\Temp\\tmp4vfkrz7o\\c6c01dc52462a49b95e2aab9f8de4f586de56234\\docs', 'C:\\Users\\riedgar\\source\\repos\\fairlearn\\build\\html\\master')' returned non-zero exit status 2.

# Fixed code
def copy_asset(
    source: str,
    destination: str,
    excluded: PathMatcher = lambda path: False,
    context: Dict = None,
    renderer: "BaseRenderer" = None,
    onerror: Callable[[str, Exception], None] = None,
) -> None:
    """Copy asset files to destination recursively.

    On copying, it expands the template variables if context argument is given and
    the asset is a template file.

    :param source: The path to source file or directory
    :param destination: The path to destination directory
    :param excluded: The matcher to determine the given path should be copied or not
    :param context: The template variables.  If not given, template files are simply copied
    :param renderer: The template engine.  If not given, SphinxRenderer is used by default
    :param onerror: The error handler.
    """
    if not os.path.exists(source):
        return

    if renderer is None:
        from sphinx.util.template import SphinxRenderer

        renderer = SphinxRenderer()

    ensuredir(destination)
    if os.path.isfile(source):
        copy_asset_file(source, destination, context, renderer)
        return

    for root, dirs, files in os.walk(source, followlinks=True):
        reldir = relative_path(source, root)
        for dir in dirs[:]:
            if excluded(posixpath.join(reldir, dir)):
                dirs.remove(dir)
            else:
                ensuredir(posixpath.join(destination, reldir, dir))

        for filename in files:
            if not excluded(posixpath.join(reldir, filename)):
                try:
                    copy_asset_file(
                        posixpath.join(root, filename),
                        posixpath.join(destination, reldir),
                        context,
                        renderer,
                    )
                except Exception as exc:
                    if onerror:
                        onerror(posixpath.join(root, filename), exc)
                    else:
                        raise