Merge "Restore <all-in> operator"
This commit is contained in:
commit
f86ca3aa22
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ast
|
||||
import operator
|
||||
|
||||
import pyparsing
|
||||
@ -20,6 +21,15 @@ from pyparsing import Literal
|
||||
from pyparsing import OneOrMore
|
||||
from pyparsing import Regex
|
||||
|
||||
|
||||
def _all_in(x, *y):
|
||||
x = ast.literal_eval(x)
|
||||
if not isinstance(x, list):
|
||||
raise TypeError("<all-in> must compare with a list literal"
|
||||
" string, EG \"%s\"" % (['aes', 'mmx'],))
|
||||
return all(val in x for val in y)
|
||||
|
||||
|
||||
op_methods = {
|
||||
# This one is special/odd,
|
||||
# TODO(harlowja): fix it so that it's not greater than or
|
||||
@ -38,6 +48,7 @@ op_methods = {
|
||||
's==': operator.eq,
|
||||
's>': operator.gt,
|
||||
's>=': operator.ge,
|
||||
'<all-in>': _all_in,
|
||||
'<in>': lambda x, y: y in x,
|
||||
'<or>': lambda x, *y: any(x == a for a in y),
|
||||
}
|
||||
@ -61,18 +72,20 @@ def make_grammar():
|
||||
# Order matters here (so that '>' doesn't match before '>=')
|
||||
Literal("s>=") | Literal("s>"))
|
||||
|
||||
all_in_nary_op = Literal("<all-in>")
|
||||
or_ = Literal("<or>")
|
||||
|
||||
# An atom is anything not an keyword followed by anything but whitespace
|
||||
atom = ~(unary_ops | or_) + Regex(r"\S+")
|
||||
atom = ~(unary_ops | all_in_nary_op | or_) + Regex(r"\S+")
|
||||
|
||||
unary = unary_ops + atom
|
||||
nary = all_in_nary_op + OneOrMore(atom)
|
||||
disjunction = OneOrMore(or_ + atom)
|
||||
|
||||
# Even-numbered tokens will be '<or>', so we drop them
|
||||
disjunction.setParseAction(lambda _s, _l, t: ["<or>"] + t[1::2])
|
||||
|
||||
expr = disjunction | unary | atom
|
||||
expr = disjunction | nary | unary | atom
|
||||
return expr
|
||||
|
||||
|
||||
@ -80,10 +93,11 @@ def match(cmp_value, spec):
|
||||
"""Match a given value to a given spec DSL."""
|
||||
expr = make_grammar()
|
||||
try:
|
||||
ast = expr.parseString(spec)
|
||||
tree = expr.parseString(spec)
|
||||
except pyparsing.ParseException:
|
||||
ast = [spec]
|
||||
if len(ast) == 1:
|
||||
return ast[0] == cmp_value
|
||||
op = op_methods[ast[0]]
|
||||
return op(cmp_value, *ast[1:])
|
||||
tree = [spec]
|
||||
if len(tree) == 1:
|
||||
return tree[0] == cmp_value
|
||||
|
||||
op = op_methods[tree[0]]
|
||||
return op(cmp_value, *tree[1:])
|
||||
|
@ -337,3 +337,86 @@ class SpecsMatcherTestCase(test_base.BaseTestCase):
|
||||
value='3.0',
|
||||
req='== 3.1',
|
||||
matches=False)
|
||||
|
||||
def test_specs_matches_all_with_op_allin(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> aes mmx',
|
||||
matches=True)
|
||||
|
||||
def test_specs_matches_one_with_op_allin(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> mmx',
|
||||
matches=True)
|
||||
|
||||
def test_specs_fails_with_op_allin(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> txt',
|
||||
matches=False)
|
||||
|
||||
def test_specs_fails_all_with_op_allin(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> txt 3dnow',
|
||||
matches=False)
|
||||
|
||||
def test_specs_fails_match_one_with_op_allin(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> txt aes',
|
||||
matches=False)
|
||||
|
||||
def test_specs_fails_match_substr_single(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['X_X']),
|
||||
req='<all-in> _',
|
||||
matches=False)
|
||||
|
||||
def test_specs_fails_match_substr(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['X___X']),
|
||||
req='<all-in> ___',
|
||||
matches=False)
|
||||
|
||||
def test_specs_fails_match_substr_reversed(self):
|
||||
self._do_specs_matcher_test(
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> XaesX',
|
||||
matches=False)
|
||||
|
||||
def test_specs_fails_onechar_with_op_allin(self):
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
specs_matcher.match,
|
||||
value=str(['aes', 'mmx', 'aux']),
|
||||
req='<all-in> e')
|
||||
|
||||
def test_specs_errors_list_with_op_allin(self):
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
specs_matcher.match,
|
||||
value=['aes', 'mmx', 'aux'],
|
||||
req='<all-in> aes')
|
||||
|
||||
def test_specs_errors_str_with_op_allin(self):
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
specs_matcher.match,
|
||||
value='aes',
|
||||
req='<all-in> aes')
|
||||
|
||||
def test_specs_errors_dict_literal_with_op_allin(self):
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
specs_matcher.match,
|
||||
value=str({'aes': 1}),
|
||||
req='<all-in> aes')
|
||||
|
||||
def test_specs_errors_bad_literal_with_op_allin(self):
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
specs_matcher.match,
|
||||
value="^&*($",
|
||||
req='<all-in> aes')
|
||||
|
Loading…
Reference in New Issue
Block a user