summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jason+github@nextthought.com>2018-09-25 12:38:30 -0500
committerGitHub <noreply@github.com>2018-09-25 12:38:30 -0500
commit6fa6e4824182f63e23d0ee36c06f4af2d5af50b2 (patch)
treeb876b4ef96299dc2a9a90e418ef7cda129c2e7a1
parent3d013be0997fc9aae6031bca4b82a9d4f2d8013c (diff)
parent1d1f97251b833ae39518b1dc3741e30bcdf61bcd (diff)
downloadzope-configuration-6fa6e4824182f63e23d0ee36c06f4af2d5af50b2.tar.gz
Merge pull request #33 from zopefoundation/issue3
Make Path and .path() expand users and environment variables.
-rw-r--r--CHANGES.rst5
-rw-r--r--docs/api/fields.rst25
-rw-r--r--docs/narr.rst2
-rw-r--r--src/zope/configuration/config.py5
-rw-r--r--src/zope/configuration/fields.py33
-rw-r--r--src/zope/configuration/interfaces.py6
-rw-r--r--src/zope/configuration/tests/test_config.py25
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()