diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index df168b495..1f6bede1a 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -10,7 +10,9 @@ """ import os import codecs -from sphinx.util.osutil import copyfile +import posixpath +from docutils.utils import relative_path +from sphinx.util.osutil import copyfile, ensuredir, walk def copy_asset_file(source, destination, context={}, renderer=None): @@ -37,3 +39,34 @@ def copy_asset_file(source, destination, context={}, renderer=None): fdst.write(renderer.render_string(fsrc.read(), context)) else: copyfile(source, destination) + + +def copy_asset(source, destination, excluded=lambda path: False, context={}, renderer=None): + """Copy asset files to destination recursively. + + On copying, it expands the template variables if 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 + :param renderer: The template engine + """ + ensuredir(destination) + if os.path.isfile(source): + copy_asset_file(source, destination, context, renderer) + return + + for root, dirs, files in walk(source): + 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) diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py index c5680c890..5e13d79b2 100644 --- a/tests/test_util_fileutil.py +++ b/tests/test_util_fileutil.py @@ -8,7 +8,7 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -from sphinx.util.fileutil import copy_asset_file +from sphinx.util.fileutil import copy_asset, copy_asset_file from sphinx.jinja2glue import BuiltinTemplateLoader from mock import Mock @@ -56,3 +56,48 @@ def test_copy_asset_file(tmpdir): copy_asset_file(src, subdir, {'var1': 'template'}, renderer) assert (subdir / 'asset.txt').exists() assert (subdir / 'asset.txt').text() == '# template data' + + +@with_tempdir +def test_copy_asset(tmpdir): + renderer = DummyTemplateLoader() + + # prepare source files + source = (tmpdir / 'source') + source.makedirs() + (source / 'index.rst').write_text('index.rst') + (source / 'foo.rst_t').write_text('{{var1}}.rst') + (source / '_static').makedirs() + (source / '_static' / 'basic.css').write_text('basic.css') + (source / '_templates').makedirs() + (source / '_templates' / 'layout.html').write_text('layout.html') + (source / '_templates' / 'sidebar.html_t').write_text('sidebar: {{var2}}') + + # copy a single file + assert not (tmpdir / 'test1').exists() + copy_asset(source / 'index.rst', tmpdir / 'test1') + assert (tmpdir / 'test1').exists() + assert (tmpdir / 'test1/index.rst').exists() + + # copy directories + destdir = tmpdir / 'test2' + copy_asset(source, destdir, context=dict(var1='bar', var2='baz'), renderer=renderer) + assert (destdir / 'index.rst').exists() + assert (destdir / 'foo.rst').exists() + assert (destdir / 'foo.rst').text() == 'bar.rst' + assert (destdir / '_static' / 'basic.css').exists() + assert (destdir / '_templates' / 'layout.html').exists() + assert (destdir / '_templates' / 'sidebar.html').exists() + assert (destdir / '_templates' / 'sidebar.html').text() == 'sidebar: baz' + + # copy with exclusion + def excluded(path): + return ('sidebar.html' in path or 'basic.css' in path) + + destdir = tmpdir / 'test3' + copy_asset(source, destdir, excluded, renderer=renderer) + assert (destdir / 'index.rst').exists() + assert (destdir / 'foo.rst').exists() + assert not (destdir / '_static' / 'basic.css').exists() + assert (destdir / '_templates' / 'layout.html').exists() + assert not (destdir / '_templates' / 'sidebar.html').exists()