diff options
author | Jason Madden <jason+github@nextthought.com> | 2018-09-25 12:38:30 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-25 12:38:30 -0500 |
commit | 6fa6e4824182f63e23d0ee36c06f4af2d5af50b2 (patch) | |
tree | b876b4ef96299dc2a9a90e418ef7cda129c2e7a1 | |
parent | 3d013be0997fc9aae6031bca4b82a9d4f2d8013c (diff) | |
parent | 1d1f97251b833ae39518b1dc3741e30bcdf61bcd (diff) | |
download | zope-configuration-6fa6e4824182f63e23d0ee36c06f4af2d5af50b2.tar.gz |
Merge pull request #33 from zopefoundation/issue3
Make Path and .path() expand users and environment variables.
-rw-r--r-- | CHANGES.rst | 5 | ||||
-rw-r--r-- | docs/api/fields.rst | 25 | ||||
-rw-r--r-- | docs/narr.rst | 2 | ||||
-rw-r--r-- | src/zope/configuration/config.py | 5 | ||||
-rw-r--r-- | src/zope/configuration/fields.py | 33 | ||||
-rw-r--r-- | src/zope/configuration/interfaces.py | 6 | ||||
-rw-r--r-- | src/zope/configuration/tests/test_config.py | 25 |
7 files changed, 93 insertions, 8 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 3cd9280..99874f1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,11 @@ Changes - Drop support for ``python setup.py test``. +- Make ``zope.configuration.fields.Path`` and + ``zope.configuration.config.ConfigurationContext`` expand + environment variables and expand user home directories in paths. See + `issue <https://github.com/zopefoundation/zope.configuration/issues/3>`_. + - Fix resolving names from a Python 2 package whose ``__init__.py`` has unicode elements in ``__all__``. diff --git a/docs/api/fields.rst b/docs/api/fields.rst index 928a632..c44dcf1 100644 --- a/docs/api/fields.rst +++ b/docs/api/fields.rst @@ -203,6 +203,16 @@ >>> n.split(os.sep) ['', 'a', 'b'] + Environment variables are expanded: + + .. doctest:: + + >>> os.environ['path-test'] = '42' + >>> with_env = os.path.join(os.sep, u'a', u'${path-test}') + >>> n = field.fromUnicode(with_env) + >>> n.split(os.sep) + ['', 'a', '42'] + Now try a relative path: .. doctest:: @@ -212,6 +222,21 @@ >>> n.split(os.sep) ['', 'faux', 'context', 'a', 'b'] + The current user is expanded (these are implicitly relative paths): + + .. doctest:: + + >>> old_home = os.environ.get('HOME') + >>> os.environ['HOME'] = os.path.join(os.sep, 'HOME') + >>> n = field.fromUnicode('~') + >>> n.split(os.sep) + ['', 'HOME'] + >>> if old_home: + ... os.environ['HOME'] = old_home + ... else: + ... del os.environ['HOME'] + + .. autoclass:: Bool :members: :member-order: bysource diff --git a/docs/narr.rst b/docs/narr.rst index 37bf25e..fc7d804 100644 --- a/docs/narr.rst +++ b/docs/narr.rst @@ -595,7 +595,7 @@ nest them under a new ``<configure>`` element. Features -------- -The verbs ``have`` and ``not-have`` test for the presence or absense +The verbs ``have`` and ``not-have`` test for the presence or absence of features in the configuration context. The argument is a single feature name. Features can be established in Python code using :meth:`~zope.configuration.config.ConfigurationContext.provideFeature`, diff --git a/src/zope/configuration/config.py b/src/zope/configuration/config.py index 487154e..774b265 100644 --- a/src/zope/configuration/config.py +++ b/src/zope/configuration/config.py @@ -31,6 +31,7 @@ from zope.configuration.interfaces import IConfigurationContext from zope.configuration.interfaces import IGroupingContext from zope.configuration.fields import GlobalInterface from zope.configuration.fields import GlobalObject +from zope.configuration.fields import PathProcessor from zope.configuration._compat import builtins from zope.configuration._compat import reraise from zope.configuration._compat import string_types @@ -185,9 +186,9 @@ class ConfigurationContext(object): def path(self, filename): """ Compute package-relative paths. """ - filename = os.path.normpath(filename) + filename, needs_processing = PathProcessor.expand(filename) - if os.path.isabs(filename): + if not needs_processing: return filename # Got a relative path, combine with base path. diff --git a/src/zope/configuration/fields.py b/src/zope/configuration/fields.py index b27a236..e934f69 100644 --- a/src/zope/configuration/fields.py +++ b/src/zope/configuration/fields.py @@ -120,19 +120,42 @@ class Tokens(List): return values +class PathProcessor(object): + # Internal helper for manipulations on paths + + @classmethod + def expand(cls, filename): + # Perform the expansions we want to have done. Returns a + # tuple: (path, needs_processing) If the second value is true, + # further processing should be done (the path isn't fully + # resolved); if false, the path should be used as is + + filename = filename.strip() + # expanding a ~ at the front should generally result + # in an absolute path. + filename = os.path.expanduser(filename) + filename = os.path.expandvars(filename) + if os.path.isabs(filename): + return os.path.normpath(filename), False + return filename, True + @implementer(IFromUnicode) class Path(Text): """A file path name, which may be input as a relative path Input paths are converted to absolute paths and normalized. + + .. versionchanged:: 4.2.0 + Start expanding home directories and environment variables. """ - def fromUnicode(self, u): - u = u.strip() - if os.path.isabs(u): - return os.path.normpath(u) - return self.context.path(u) + def fromUnicode(self, value): + filename, needs_processing = PathProcessor.expand(value) + if needs_processing: + filename = self.context.path(filename) + + return filename @implementer(IFromUnicode) diff --git a/src/zope/configuration/interfaces.py b/src/zope/configuration/interfaces.py index 67ee132..573e929 100644 --- a/src/zope/configuration/interfaces.py +++ b/src/zope/configuration/interfaces.py @@ -64,6 +64,12 @@ class IConfigurationContext(Interface): If the filename is relative to the package, then the returned name will include the package path, otherwise, the original file name is returned. + + Environment variables in the path are expanded, and if the path + begins with the username marker (~), that is expanded as well. + + .. versionchanged:: 4.2.0 + Start expanding home directories and environment variables. """ def checkDuplicate(filename): diff --git a/src/zope/configuration/tests/test_config.py b/src/zope/configuration/tests/test_config.py index ed86737..4835df9 100644 --- a/src/zope/configuration/tests/test_config.py +++ b/src/zope/configuration/tests/test_config.py @@ -163,6 +163,31 @@ class ConfigurationContextTests(unittest.TestCase): c.package = stub() self.assertTrue(os.path.isabs(c.path('y/z'))) + def test_path_expand_filenames(self): + import os + c = self._makeOne() + os.environ['path-test'] = '42' + try: + path = c.path(os.path.join(os.sep, '${path-test}')) + self.assertEqual(path, os.path.join(os.sep, '42')) + finally: + del os.environ['path-test'] + + def test_path_expand_user(self): + import os + c = self._makeOne() + old_home = os.environ.get('HOME') + # HOME should be absolute + os.environ['HOME'] = os.path.join(os.sep, 'HOME') + try: + path = c.path('~') + self.assertEqual(path, os.path.join(os.sep, 'HOME')) + finally: # pragma: no cover + if old_home: + os.environ['HOME'] = old_home + else: + del os.environ['HOME'] + def test_checkDuplicate_miss(self): import os c = self._makeOne() |