Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #1681 (globbing support for [options.data_files]) #2712

Merged
merged 8 commits into from
Aug 26, 2021
1 change: 1 addition & 0 deletions changelog.d/2712.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `glob:` directive for use inside `[options.data_files]` values.
5 changes: 4 additions & 1 deletion docs/userguide/declarative_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 = glob: data/fonts/*.ttf, glob: data/fonts/*.otf

Metadata and options are set in the config sections of the same name.

Expand Down Expand Up @@ -156,6 +157,8 @@ Special directives:
The ``file:`` directive is sandboxed and won't reach anything outside
the directory containing ``setup.py``.

* ``glob:`` - Value will be treated as a glob() pattern and expanded accordingly.


Metadata
--------
Expand Down Expand Up @@ -225,7 +228,7 @@ package_data section [#
exclude_package_data section
namespace_packages list-comma
py_modules list-comma
data_files dict 40.6.0
data_files glob:, dict 40.6.0
======================= =================================== =============== =========

**Notes**:
Expand Down
31 changes: 30 additions & 1 deletion setuptools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from collections import defaultdict
from functools import partial
from functools import wraps
from glob import iglob
import contextlib

from distutils.errors import DistutilsOptionError, DistutilsFileError
Expand Down Expand Up @@ -255,6 +256,34 @@ def _parse_list(cls, value, separator=','):

return [chunk.strip() for chunk in value if chunk.strip()]

@classmethod
def _parse_list_glob(cls, value, separator=','):
"""Equivalent to _parse_list() but expands 'glob:' directives using glob().

However, unlike glob(), the resolved results stay relative paths.

:param value:
:param separator: List items separator character.
:rtype: list
"""
directive = 'glob:'
values = cls._parse_list(value, separator=separator)
expanded_values = []
for value in values:
trimmed_value = value.strip()
if trimmed_value.startswith(directive):
# take what is after "glob:"
value = trimmed_value.split(directive, 1)[-1].strip()
value = os.path.abspath(value)
# and expand it while keeping as a relative path:
value = sorted(
os.path.relpath(path, os.getcwd()) for path in iglob(value))
expanded_values.extend(value)
else:
expanded_values.append(value)

return expanded_values

@classmethod
def _parse_dict(cls, value):
"""Represents value as a dict.
Expand Down Expand Up @@ -711,5 +740,5 @@ def parse_section_data_files(self, section_options):

: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()]
35 changes: 35 additions & 0 deletions setuptools/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,41 @@ def test_data_files(self, tmpdir):
]
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 = glob: *.dat\n'
'icons = \n'
' glob: *.ico\n'
'audio = \n'
' glob:*.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,
Expand Down