summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/2712.change.rst1
-rw-r--r--docs/userguide/declarative_config.rst1
-rw-r--r--setuptools/config.py31
-rw-r--r--setuptools/tests/test_config.py35
4 files changed, 67 insertions, 1 deletions
diff --git a/changelog.d/2712.change.rst b/changelog.d/2712.change.rst
new file mode 100644
index 00000000..952101f1
--- /dev/null
+++ b/changelog.d/2712.change.rst
@@ -0,0 +1 @@
+Added implicit globbing support for `[options.data_files]` values.
diff --git a/docs/userguide/declarative_config.rst b/docs/userguide/declarative_config.rst
index 128d9f97..3b72611c 100644
--- a/docs/userguide/declarative_config.rst
+++ b/docs/userguide/declarative_config.rst
@@ -68,6 +68,7 @@ boilerplate code in some cases.
site.d/00_default.conf
host.d/00_default.conf
data = data/img/logo.png, data/svg/icon.svg
+ fonts = data/fonts/*.ttf, data/fonts/*.otf
Metadata and options are set in the config sections of the same name.
diff --git a/setuptools/config.py b/setuptools/config.py
index 44de7cf5..7ab6bfd7 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -9,6 +9,7 @@ import importlib
from collections import defaultdict
from functools import partial
from functools import wraps
+from glob import iglob
import contextlib
from distutils.errors import DistutilsOptionError, DistutilsFileError
@@ -256,6 +257,34 @@ class ConfigHandler:
return [chunk.strip() for chunk in value if chunk.strip()]
@classmethod
+ def _parse_list_glob(cls, value, separator=','):
+ """Equivalent to _parse_list() but expands any glob patterns using glob().
+
+ However, unlike with glob() calls, the results remain relative paths.
+
+ :param value:
+ :param separator: List items separator character.
+ :rtype: list
+ """
+ glob_characters = ('*', '?', '[', ']', '{', '}')
+ values = cls._parse_list(value, separator=separator)
+ expanded_values = []
+ for value in values:
+
+ # Has globby characters?
+ if any(char in value for char in glob_characters):
+ # then expand the glob pattern while keeping paths *relative*:
+ expanded_values.extend(sorted(
+ os.path.relpath(path, os.getcwd())
+ for path in iglob(os.path.abspath(value))))
+
+ else:
+ # take the value as-is:
+ expanded_values.append(value)
+
+ return expanded_values
+
+ @classmethod
def _parse_dict(cls, value):
"""Represents value as a dict.
@@ -711,5 +740,5 @@ class ConfigOptionsHandler(ConfigHandler):
:param dict section_options:
"""
- parsed = self._parse_section_to_dict(section_options, self._parse_list)
+ parsed = self._parse_section_to_dict(section_options, self._parse_list_glob)
self['data_files'] = [(k, v) for k, v in parsed.items()]
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 21f1becd..ec65250e 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -893,6 +893,41 @@ class TestOptions:
]
assert sorted(dist.data_files) == sorted(expected)
+ def test_data_files_globby(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.data_files]\n'
+ 'cfg =\n'
+ ' a/b.conf\n'
+ ' c/d.conf\n'
+ 'data = *.dat\n'
+ 'icons = \n'
+ ' *.ico\n'
+ 'audio = \n'
+ ' *.wav\n'
+ ' sounds.db\n'
+ )
+
+ # Create dummy files for glob()'s sake:
+ tmpdir.join('a.dat').write('')
+ tmpdir.join('b.dat').write('')
+ tmpdir.join('c.dat').write('')
+ tmpdir.join('a.ico').write('')
+ tmpdir.join('b.ico').write('')
+ tmpdir.join('c.ico').write('')
+ tmpdir.join('beep.wav').write('')
+ tmpdir.join('boop.wav').write('')
+ tmpdir.join('sounds.db').write('')
+
+ with get_dist(tmpdir) as dist:
+ expected = [
+ ('cfg', ['a/b.conf', 'c/d.conf']),
+ ('data', ['a.dat', 'b.dat', 'c.dat']),
+ ('icons', ['a.ico', 'b.ico', 'c.ico']),
+ ('audio', ['beep.wav', 'boop.wav', 'sounds.db']),
+ ]
+ assert sorted(dist.data_files) == sorted(expected)
+
def test_python_requires_simple(self, tmpdir):
fake_env(
tmpdir,