Skip to content

Commit

Permalink
Merge branch 'ternary-interpolation' of git://github.com/jaredgrubb/b…
Browse files Browse the repository at this point in the history
…uildbot

* 'ternary-interpolation' of git://github.com/jaredgrubb/buildbot:
  ternary-sub: add docs
  Fixes #2165: Add 'ternary' interpolation for WithProperties
  • Loading branch information
djmitche committed May 12, 2012
2 parents f518b0b + 805ef88 commit 5cf757d
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 3 deletions.
70 changes: 67 additions & 3 deletions master/buildbot/process/properties.py
Expand Up @@ -185,6 +185,17 @@ class _PropertyMap(object):
colon_minus_re = re.compile(r"(.*):-(.*)")
colon_tilde_re = re.compile(r"(.*):~(.*)")
colon_plus_re = re.compile(r"(.*):\+(.*)")

colon_ternary_re = re.compile(r"""(?P<prop>.*) # the property to match
: # colon
(?P<alt>\#)? # might have the alt marker '#'
\? # question mark
(?P<delim>.) # the delimiter
(?P<true>.*) # sub-if-true
(?P=delim) # the delimiter again
(?P<false>.*)# sub-if-false
""", re.VERBOSE)

def __init__(self, properties):
# use weakref here to avoid a reference loop
self.properties = weakref.ref(properties)
Expand Down Expand Up @@ -225,10 +236,38 @@ def colon_plus(mo):
else:
return ''

def colon_ternary(mo):
# %(prop:?:T:F)s
# if prop exists, use T; otherwise, F
# %(prop:#?:T:F)s
# if prop is true, use T; otherwise, F
groups = mo.groupdict()

prop = groups['prop']

if prop in self.temp_vals:
if groups['alt']:
use_true = self.temp_vals[prop]
else:
use_true = True
elif properties.has_key(prop):
if groups['alt']:
use_true = properties[prop]
else:
use_true = True
else:
use_true = False

if use_true:
return groups['true']
else:
return groups['false']

for regexp, fn in [
( self.colon_minus_re, colon_minus ),
( self.colon_tilde_re, colon_tilde ),
( self.colon_plus_re, colon_plus ),
( self.colon_ternary_re, colon_ternary ),
]:
mo = regexp.match(key)
if mo:
Expand Down Expand Up @@ -436,20 +475,45 @@ def _parseColon_plus(self, d, kw, repl):
defaultWhenFalse=False,
elideNoneAs='')

colon_ternary_re = re.compile(r"""(?P<delim>.) # the delimiter
(?P<true>.*) # sub-if-true
(?P=delim) # the delimiter again
(?P<false>.*)# sub-if-false
""", re.VERBOSE)

def _parseColon_ternary(self, d, kw, repl, defaultWhenFalse=False):
m = self.colon_ternary_re.match(repl)
if not m:
config.error("invalid Interpolate ternary expression for selector '%s' and delim '%s'" % (kw, repl[0]))
return None
m = m.groupdict()
return _Lookup(d, kw,
hasKey=Interpolate(m['true'], **self.kwargs),
default=Interpolate(m['false'], **self.kwargs),
defaultWhenFalse=defaultWhenFalse,
elideNoneAs='')

def _parseColon_ternary_hash(self, d, kw, repl):
return self._parseColon_ternary(d, kw, repl, defaultWhenFalse=True)

def _parse(self, fmtstring):
keys = _getInterpolationList(fmtstring)
for key in keys:
if not self.interpolations.has_key(key):
d, kw, repl = self._parseSubstitution(key)
if repl is None:
repl = '-'
for char, fn in [
for pattern, fn in [
( "-", self._parseColon_minus ),
( "~", self._parseColon_tilde ),
( "+", self._parseColon_plus ),
( "?", self._parseColon_ternary ),
( "#?", self._parseColon_ternary_hash )
]:
if repl[0] == char:
self.interpolations[key] = fn(d, kw, repl[1:])
junk, matches, tail = repl.partition(pattern)
if not junk and matches:
self.interpolations[key] = fn(d, kw, tail)
break
if not self.interpolations.has_key(key):
config.error("invalid Interpolate default type '%s'" % repl[0])

Expand Down
213 changes: 213 additions & 0 deletions master/buildbot/test/unit/test_process_properties.py
Expand Up @@ -197,6 +197,58 @@ def testColonPlusEmpty(self):
def testColonPlusUnset(self):
return self.doTestSimpleWithProperties('%(prop_nosuch:+present)s', '')


def testColonTernarySet(self):
return self.doTestSimpleWithProperties('%(prop_str:?:present:missing)s', 'present')

def testColonTernaryNone(self):
return self.doTestSimpleWithProperties('%(prop_none:?:present:missing)s', 'present')

def testColonTernaryZero(self):
return self.doTestSimpleWithProperties('%(prop_zero:?|present|missing)s', 'present')

def testColonTernaryOne(self):
return self.doTestSimpleWithProperties('%(prop_one:?:present:missing)s', 'present')

def testColonTernaryFalse(self):
return self.doTestSimpleWithProperties('%(prop_false:?|present|missing)s', 'present')

def testColonTernaryTrue(self):
return self.doTestSimpleWithProperties('%(prop_true:?:present:missing)s', 'present')

def testColonTernaryEmpty(self):
return self.doTestSimpleWithProperties('%(prop_empty:?ApresentAmissing)s', 'present')

def testColonTernaryUnset(self):
return self.doTestSimpleWithProperties('%(prop_nosuch:?#present#missing)s', 'missing')


def testColonTernaryHashSet(self):
return self.doTestSimpleWithProperties('%(prop_str:#?:truish:falsish)s', 'truish')

def testColonTernaryHashNone(self):
# None is special-cased *differently* for '#?'
return self.doTestSimpleWithProperties('%(prop_none:#?|truish|falsish)s', 'falsish')

def testColonTernaryHashZero(self):
return self.doTestSimpleWithProperties('%(prop_zero:#?:truish:falsish)s', 'falsish')

def testColonTernaryHashOne(self):
return self.doTestSimpleWithProperties('%(prop_one:#?:truish:falsish)s', 'truish')

def testColonTernaryHashFalse(self):
return self.doTestSimpleWithProperties('%(prop_false:#?:truish:falsish)s', 'falsish')

def testColonTernaryHashTrue(self):
return self.doTestSimpleWithProperties('%(prop_true:#?|truish|falsish)s', 'truish')

def testColonTernaryHashEmpty(self):
return self.doTestSimpleWithProperties('%(prop_empty:#?:truish:falsish)s', 'falsish')

def testColonTernaryHashUnset(self):
return self.doTestSimpleWithProperties('%(prop_nosuch:#?.truish.falsish)s', 'falsish')


def testClearTempValues(self):
d = self.doTestSimpleWithProperties('', '',
prop_temp=lambda b: 'present')
Expand Down Expand Up @@ -274,6 +326,23 @@ def testTempValuePlusUnsetSet(self):
prop_nosuch=lambda b: 1)


def testTempValueColonTernaryTrue(self):
return self.doTestSimpleWithProperties('%(prop_temp:?:present:missing)s', 'present',
prop_temp=lambda b: True)

def testTempValueColonTernaryFalse(self):
return self.doTestSimpleWithProperties('%(prop_temp:?|present|missing)s', 'present',
prop_temp=lambda b: False)

def testTempValueColonTernaryHashTrue(self):
return self.doTestSimpleWithProperties('%(prop_temp:#?|truish|falsish)s', 'truish',
prop_temp=lambda b: 1)

def testTempValueColonTernaryHashFalse(self):
return self.doTestSimpleWithProperties('%(prop_temp:#?|truish|falsish)s', 'falsish',
prop_nosuch=lambda b: 0)


class TestInterpolateConfigure(unittest.TestCase, ConfigErrorsMixin):
"""
Test that Interpolate reports erros in the interpolation string
Expand Down Expand Up @@ -314,6 +383,14 @@ def test_nested_invalid_selector(self):
lambda: Interpolate("%(prop:some_prop:~%(garbage:test)s)s"))


def test_colon_ternary_bad_delimeter(self):
self.assertRaisesConfigError("invalid Interpolate ternary expression for selector 'P' and delim ':'",
lambda: Interpolate("echo '%(prop:P:?:one)s'"))

def test_colon_ternary_hash_bad_delimeter(self):
self.assertRaisesConfigError("invalid Interpolate ternary expression for selector 'P' and delim '|'",
lambda: Interpolate("echo '%(prop:P:#?|one)s'"))


class TestInterpolatePositional(unittest.TestCase):
def setUp(self):
Expand Down Expand Up @@ -417,6 +494,133 @@ def test_nested_property(self):
"echo 'so long!'")
return d

def test_nested_property_deferred(self):
renderable = DeferredRenderable()
self.props.setProperty("missing", renderable, "test")
self.props.setProperty("project", "so long!", "test")
command = Interpolate("echo '%(prop:missing:~%(prop:project)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'so long!'")
renderable.callback(False)
return d

def test_property_substitute_recursively(self):
self.props.setProperty("project", "proj1", "test")
command = Interpolate("echo '%(prop:no_such:-%(prop:project)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'proj1'")
return d

def test_property_colon_ternary_present(self):
self.props.setProperty("project", "proj1", "test")
command = Interpolate("echo %(prop:project:?:defined:missing)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo defined")
return d

def test_property_colon_ternary_missing(self):
command = Interpolate("echo %(prop:project:?|defined|missing)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo missing")
return d

def test_property_colon_ternary_hash_true(self):
self.props.setProperty("project", "winbld", "test")
command = Interpolate("echo buildby-%(prop:project:#?:T:F)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo buildby-T")
return d

def test_property_colon_ternary_hash_false(self):
self.props.setProperty("project", "", "test")
command = Interpolate("echo buildby-%(prop:project:#?|T|F)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo buildby-F")
return d

def test_property_colon_ternary_substitute_recursively_true(self):
self.props.setProperty("P", "present", "test")
self.props.setProperty("one", "proj1", "test")
self.props.setProperty("two", "proj2", "test")
command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'proj1'")
return d

def test_property_colon_ternary_substitute_recursively_false(self):
self.props.setProperty("one", "proj1", "test")
self.props.setProperty("two", "proj2", "test")
command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'proj2'")
return d

def test_property_substitute_recursively(self):
self.props.setProperty("project", "proj1", "test")
command = Interpolate("echo '%(prop:no_such:-%(prop:project)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'proj1'")
return d

def test_property_colon_ternary_present(self):
self.props.setProperty("project", "proj1", "test")
command = Interpolate("echo %(prop:project:?:defined:missing)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo defined")
return d

def test_property_colon_ternary_missing(self):
command = Interpolate("echo %(prop:project:?|defined|missing)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo missing")
return d

def test_property_colon_ternary_hash_true(self):
self.props.setProperty("project", "winbld", "test")
command = Interpolate("echo buildby-%(prop:project:#?:T:F)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo buildby-T")
return d

def test_property_colon_ternary_hash_false(self):
self.props.setProperty("project", "", "test")
command = Interpolate("echo buildby-%(prop:project:#?|T|F)s")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo buildby-F")
return d

def test_property_colon_ternary_substitute_recursively_true(self):
self.props.setProperty("P", "present", "test")
self.props.setProperty("one", "proj1", "test")
self.props.setProperty("two", "proj2", "test")
command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'proj1'")
return d

def test_property_colon_ternary_substitute_recursively_false(self):
self.props.setProperty("one", "proj1", "test")
self.props.setProperty("two", "proj2", "test")
command = Interpolate("echo '%(prop:P:?|%(prop:one)s|%(prop:two)s)s'")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"echo 'proj2'")
return d

class TestInterpolateSrc(unittest.TestCase):
def setUp(self):
self.props = Properties()
Expand Down Expand Up @@ -716,6 +920,15 @@ def testDictColonPlus(self):
"build-exists-.tar.gz")
return d

def testDictColonTernary(self):
# test dict-style substitution with WithProperties
self.props.setProperty("prop1", "foo", "test")
command = WithProperties("build-%(prop1:?:exists:missing)s-%(prop2:?:exists:missing)s.tar.gz")
d = self.build.render(command)
d.addCallback(self.failUnlessEqual,
"build-exists-missing.tar.gz")
return d

def testEmpty(self):
# None should render as ''
self.props.setProperty("empty", None, "test")
Expand Down
11 changes: 11 additions & 0 deletions master/docs/manual/cfg-properties.rst
Expand Up @@ -248,6 +248,17 @@ syntaxes in the parentheses.
If ``propname`` exists, substitute ``replacement``; otherwise,
substitute an empty string.

``propname:?:sub_if_true:sub_if_false``

``propname:#?:sub_if_exists:sub_if_missing``
Ternary substitution, depending on either ``propname`` being
``True`` (like ``:~``) or being present (like ``:+``). Notice that
there is a colon immediately following the question mark *and* between
the two substitution alternatives. The character that follows the question
mark is used as the delimeter between the two alternatives. In the above
examples, it is a colon, but any single character can be used.


Although these are similar to shell substitutions, no other
substitutions are currently supported, and ``replacement`` in the
above cannot contain more substitutions.
Expand Down
3 changes: 3 additions & 0 deletions master/docs/release-notes.rst
Expand Up @@ -90,6 +90,9 @@ Features
* ``Git`` has a new ``getDescription`` option, which will run `git describe` after checkout
normally. See the documentation for details.

* A new ternary substitution operator ``:?:`` and ``:#?:`` to use with the ``Interpolate``
and ``WithProperties`` classes.

Slave
-----

Expand Down

0 comments on commit 5cf757d

Please sign in to comment.