[Checkins] SVN: ZConfig/branches/haufe-legacy-integration/ - Launchpad #373609: added support for function interpolation
Andreas Jung
andreas at andreas-jung.com
Sun May 10 12:14:42 EDT 2009
Log message for revision 99828:
- Launchpad #373609: added support for function interpolation
Changed:
U ZConfig/branches/haufe-legacy-integration/NEWS.txt
U ZConfig/branches/haufe-legacy-integration/ZConfig/__init__.py
U ZConfig/branches/haufe-legacy-integration/ZConfig/substitution.py
U ZConfig/branches/haufe-legacy-integration/ZConfig/tests/test_subst.py
-=-
Modified: ZConfig/branches/haufe-legacy-integration/NEWS.txt
===================================================================
--- ZConfig/branches/haufe-legacy-integration/NEWS.txt 2009-05-10 16:09:52 UTC (rev 99827)
+++ ZConfig/branches/haufe-legacy-integration/NEWS.txt 2009-05-10 16:14:42 UTC (rev 99828)
@@ -7,6 +7,8 @@
- Launchpad #373614: new ``includeGlobPattern`` directive
+- Launchpad #373609: added support for function interpolation
+
ZConfig 2.6.1 (2008/12/05)
--------------------------
Modified: ZConfig/branches/haufe-legacy-integration/ZConfig/__init__.py
===================================================================
--- ZConfig/branches/haufe-legacy-integration/ZConfig/__init__.py 2009-05-10 16:09:52 UTC (rev 99827)
+++ ZConfig/branches/haufe-legacy-integration/ZConfig/__init__.py 2009-05-10 16:14:42 UTC (rev 99828)
@@ -159,3 +159,6 @@
self.name = name
ConfigurationSyntaxError.__init__(
self, "no replacement for " + `name`, url, lineno)
+
+class SubstitutionUnknownFunctionError(SubstitutionReplacementError):
+ _messagePrefix = "no definition for function "
Modified: ZConfig/branches/haufe-legacy-integration/ZConfig/substitution.py
===================================================================
--- ZConfig/branches/haufe-legacy-integration/ZConfig/substitution.py 2009-05-10 16:09:52 UTC (rev 99827)
+++ ZConfig/branches/haufe-legacy-integration/ZConfig/substitution.py 2009-05-10 16:14:42 UTC (rev 99828)
@@ -15,7 +15,9 @@
import ZConfig
+from functions import resolveFunction
+
def substitute(s, mapping):
"""Interpolate variables from `mapping` into `s`."""
if "$" in s:
@@ -25,7 +27,10 @@
p, name, namecase, rest = _split(rest)
result += p
if name:
- v = mapping.get(name)
+ if isinstance(name, _Function):
+ v, rest = name(mapping)
+ else:
+ v = mapping.get(name)
if v is None:
raise ZConfig.SubstitutionReplacementError(s, namecase)
result += v
@@ -69,6 +74,14 @@
if not s.startswith("}", i - 1):
raise ZConfig.SubstitutionSyntaxError(
"'${%s' not followed by '}'" % name)
+ elif c == "(":
+ m = _name_match(s, i + 2)
+ if not m:
+ raise ZConfig.SubstitutionSyntaxError(
+ "'$(' not followed by name")
+ name = m.group(0)
+ i = m.end() + 1
+ return prefix, _Function(s, m), None, None
else:
m = _name_match(s, i+1)
if not m:
@@ -84,3 +97,64 @@
import re
_name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match
del re
+
+
+class _Function:
+ '''encapsulates a function call substitution.
+
+ A function call has the syntax '$(name args)'
+ where *args* is an optionally empty sequence of arguments.
+ Argumens in *args* are comma separated.
+ Comma can be escaped by duplication.
+ Arguments are 'stripped' and then substitution is applied
+ to them.
+
+ Note: We currently do not allow parenthesis (neither open nor closed)
+ in arguments. Use a definition, should you need such characters
+ in your arguments.
+ '''
+ def __init__(self, s, match):
+ '''function instance for function identified by *match* object in string *s*.'''
+ name = s[match.start():match.end()]
+ f = resolveFunction(name)
+ if f is None:
+ raise ZConfig.SubstitutionUnknownFunctionError(s, name)
+ self._function = f
+ # parse arguments
+ i = match.end()
+ self._args = args = []
+ if i >= len(s):
+ raise ZConfig.SubstitutionSyntaxError("'$(%s' is not closed" % name)
+ if s[i] == ')':
+ self._rest = s[i+1:]
+ return
+ if not s[i].isspace():
+ raise ZConfig.SubstitutionSyntaxError("'$(%s' not followed by either ')' or whitespace" % name)
+
+ i += 1; arg = ''
+ while i < len(s):
+ c = s[i]; i += 1
+ if c in '(': # forbidden characters
+ raise ZConfig.SubstitutionSyntaxError("'$(%s' contains forbidden character '%c'" % (name, c))
+ if c not in ',)':
+ arg += c; continue
+ if c == ',':
+ if i < len(s) and s[i] == c: # excaped
+ arg += c; i += 1
+ continue
+ args.append(arg.strip()); arg = ''
+ if c == ')': # end of function call
+ self._rest = s[i:]
+ return
+ raise ZConfig.SubstitutionSyntaxError("'$(%s' is not closed" % name)
+
+ def __call__(self, mapping):
+ '''call the function.
+
+ Arguments are substitution expanded via *mapping*.
+
+ Returns text for function call and remaining text.
+ '''
+ args = [substitute(arg, mapping) for arg in self._args]
+ v = self._function(mapping, *args)
+ return v, self._rest
Modified: ZConfig/branches/haufe-legacy-integration/ZConfig/tests/test_subst.py
===================================================================
--- ZConfig/branches/haufe-legacy-integration/ZConfig/tests/test_subst.py 2009-05-10 16:09:52 UTC (rev 99827)
+++ ZConfig/branches/haufe-legacy-integration/ZConfig/tests/test_subst.py 2009-05-10 16:14:42 UTC (rev 99828)
@@ -18,7 +18,8 @@
import unittest
-from ZConfig import SubstitutionReplacementError, SubstitutionSyntaxError
+from ZConfig import SubstitutionReplacementError, SubstitutionSyntaxError, \
+ SubstitutionUnknownFunctionError
from ZConfig.substitution import isname, substitute
@@ -89,7 +90,51 @@
self.assert_(not isname("abc-"))
self.assert_(not isname(""))
+ def test_functions(self):
+ from ZConfig.functions import registerFunction
+ registerFunction('f', lambda mapping, *args: str(args), True)
+ # check syntax
+ def check(s):
+ self.assertRaises(SubstitutionSyntaxError,
+ substitute, s, {})
+ check('$(')
+ check('$(f')
+ check('$(f,')
+ check('$(f a,b,c')
+ check('$(f a,(b),c')
+ # check arguments
+ def check(*args):
+ argstring = ', '.join(args)
+ if args: argstring = ' ' + argstring
+ v = substitute('$(f%s)' % argstring, {})
+ self.assertEqual(v, str(tuple([arg.strip() for arg in args])))
+ check()
+ check('')
+ check('','','')
+ check(' a ', 'bc ', ' xy')
+ # check correct left and right boundaries
+ self.assertEqual(substitute('a $(f) b',{}), 'a () b')
+ self.assertEqual(substitute('a$(f)b',{}), 'a()b')
+ self.assertEqual(substitute('a$(f)b$(f)',{}), 'a()b()')
+ # check interpolation in arguments
+ self.assertEqual(substitute('$(F $a)',{'a' : 'A'}), str(('A',)))
+ # check unknown function
+ self.assertRaises(SubstitutionUnknownFunctionError,
+ substitute,'$(g)',{}
+ )
+
+ def test_function_env(self):
+ from os import environ
+ key = 'ZCONFIG_TEST_ENV_FUNCTION'; key2 = key + '_none'
+ environ[key] = '1'
+ self.assertEqual(substitute('$(env %s)' % key, {}), '1')
+ self.assertEqual(substitute('$(env %s, 0)' % key2, {}), '0')
+ self.assertRaises(KeyError,
+ substitute,'$(env %s)' % key2, {}
+ )
+
+
def test_suite():
return unittest.makeSuite(SubstitutionTestCase)
More information about the Checkins
mailing list