Skip to content

Commit

Permalink
Merge branch 'feature/control-flow' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
etandel committed Jun 15, 2017
2 parents 5b6890b + 5da4a93 commit a57307f
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
1 change: 1 addition & 0 deletions jsonbender/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from jsonbender.list_ops import FlatForall, Forall, Filter, Reduce
from jsonbender.string_ops import Format
from jsonbender.selectors import F, K, S, OptionalS
from jsonbender.control_flow import Alternation, If, Switch


__version__ = '0.8.1'
Expand Down
109 changes: 109 additions & 0 deletions jsonbender/control_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from jsonbender.core import Bender
from jsonbender.selectors import K


class If(Bender):
"""
Takes a condition bender, and two benders (both default to K(None)).
If the condition bender evaluates to true, return the value of the first
bender. If it evaluates to false, return the value of the second bender.
Example:
```
if_ = If(S('country') == K('China'), S('first_name'), S('last_name'))
if_({'country': 'China',
'first_name': 'Li',
'last_name': 'Na'}) # -> 'Li'
if_({'country': 'Brazil',
'first_name': 'Gustavo',
'last_name': 'Kuerten'}) # -> 'Kuerten'
```
"""

def __init__(self, condition, when_true=K(None), when_false=K(None)):
self.condition = condition
self.when_true = when_true
self.when_false = when_false

def execute(self, val):
return (self.when_true(val)
if self.condition(val)
else self.when_false(val))


class Alternation(Bender):
"""
Take any number of benders, and return the value of the first one that
doesn't raise a LookupError (KeyError, IndexError etc.).
If all benders raise LookupError, re-raise the last raised exception.
Example:
```
b = Alternation(S(1), S(0), S('key1'))
b(['a', 'b']) # -> 'b'
b(['a']) # -> 'a'
b([]) # -> KeyError
b({}) # -> KeyError
b({'key1': 23}) # -> 23
```
"""

def __init__(self, *benders):
self.benders = benders

def execute(self, source):
exc = ValueError()
for bender in self.benders:
try:
result = bender(source)
except LookupError as e:
exc = e
else:
return result
else:
raise exc


class Switch(Bender):
"""
Take a key bender, a 'case' container of benders and a default bender
(optional).
The value returned by the key bender is used to get a bender from the
case container, which then returns the result.
If the key is not in the case container, the default is used.
If it's unavailable, raise the original LookupError.
Example:
```
b = Switch(S('service'),
{'twitter': S('handle'),
'mastodon': S('handle') + K('@') + S('server')},
default=S('email'))
b({'service': 'twitter', 'handle': 'etandel'}) # -> 'etandel'
b({'service': 'mastodon', 'handle': 'etandel',
'server': 'mastodon.social'}) # -> '[email protected]'
b({'service': 'facebook',
'email': '[email protected]'}) # -> '[email protected]'
```
"""

def __init__(self, key_bender, cases, default=None):
self.key_bender = key_bender
self.cases = cases
self.default = default

def execute(self, source):
key = self.key_bender(source)
try:
bender = self.cases[key]
except LookupError:
if self.default:
bender = self.default
else:
raise

return bender(source)

82 changes: 82 additions & 0 deletions tests/test_control_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from operator import add
import unittest

from jsonbender import Context, K, S, bend
from jsonbender.control_flow import If, Alternation, Switch
from jsonbender.test import BenderTestMixin


class TestIf(BenderTestMixin, unittest.TestCase):
def setUp(self):
self.na_li = {'country': 'China',
'first_name': 'Li',
'last_name': 'Na'}
self.guga = {'country': 'Brazil',
'first_name': 'Gustavo',
'last_name': 'Kuerten'}

def test_if_true(self):
if_ = If(S('country') == K('China'), S('first_name'), S('last_name'))
self.assert_bender(if_, self.na_li, 'Li')

def test_if_false(self):
if_ = If(S('country') == K('China'), S('first_name'), S('last_name'))
self.assert_bender(if_, self.guga, 'Kuerten')

def test_if_true_default(self):
if_ = If(S('country') == K('China'), when_false=S('last_name'))
self.assert_bender(if_, self.na_li, None)

def test_if_false_default(self):
if_ = If(S('country') == K('China'), S('first_name'))
self.assert_bender(if_, self.guga, None)


class TestAlternation(BenderTestMixin, unittest.TestCase):
def test_empty_benders(self):
self.assertRaises(ValueError, Alternation(), {})

def test_matches(self):
bender = Alternation(S(1), S(0), S('key1'))
self.assert_bender(bender, ['a', 'b'], 'b')
self.assert_bender(bender, ['a'], 'a')
self.assert_bender(bender, {'key1': 23}, 23)

def test_no_match(self):
self.assertRaises(IndexError, Alternation(S(1)), [])
self.assertRaises(KeyError, Alternation(S(1)), {})


class TestSwitch(BenderTestMixin, unittest.TestCase):
def test_match(self):
bender = Switch(S('service'),
{'twitter': S('handle'),
'mastodon': S('handle') + K('@') + S('server')},
default=S('email'))

self.assert_bender(bender,
{'service': 'twitter', 'handle': 'etandel'},
'etandel')
self.assert_bender(bender,
{'service': 'mastodon',
'handle': 'etandel',
'server': 'mastodon.social'},
'[email protected]')

def test__no_match_with_default(self):
bender = Switch(S('service'),
{'twitter': S('handle'),
'mastodon': S('handle') + K('@') + S('server')},
default=S('email'))
self.assert_bender(bender,
{'service': 'facebook',
'email': '[email protected]'},
'[email protected]')

def test__no_match_without_default(self):
self.assertRaises(KeyError, Switch(S('key'), {}), {'key': None})


if __name__ == '__main__':
unittest.main()

0 comments on commit a57307f

Please sign in to comment.