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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import ast
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
import pyparsing
|
import pyparsing
|
||||||
@ -20,6 +21,15 @@ from pyparsing import Literal
|
|||||||
from pyparsing import OneOrMore
|
from pyparsing import OneOrMore
|
||||||
from pyparsing import Regex
|
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 = {
|
op_methods = {
|
||||||
# This one is special/odd,
|
# This one is special/odd,
|
||||||
# TODO(harlowja): fix it so that it's not greater than or
|
# TODO(harlowja): fix it so that it's not greater than or
|
||||||
@ -38,6 +48,7 @@ op_methods = {
|
|||||||
's==': operator.eq,
|
's==': operator.eq,
|
||||||
's>': operator.gt,
|
's>': operator.gt,
|
||||||
's>=': operator.ge,
|
's>=': operator.ge,
|
||||||
|
'<all-in>': _all_in,
|
||||||
'<in>': lambda x, y: y in x,
|
'<in>': lambda x, y: y in x,
|
||||||
'<or>': lambda x, *y: any(x == a for a in y),
|
'<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 '>=')
|
# Order matters here (so that '>' doesn't match before '>=')
|
||||||
Literal("s>=") | Literal("s>"))
|
Literal("s>=") | Literal("s>"))
|
||||||
|
|
||||||
|
all_in_nary_op = Literal("<all-in>")
|
||||||
or_ = Literal("<or>")
|
or_ = Literal("<or>")
|
||||||
|
|
||||||
# An atom is anything not an keyword followed by anything but whitespace
|
# 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
|
unary = unary_ops + atom
|
||||||
|
nary = all_in_nary_op + OneOrMore(atom)
|
||||||
disjunction = OneOrMore(or_ + atom)
|
disjunction = OneOrMore(or_ + atom)
|
||||||
|
|
||||||
# Even-numbered tokens will be '<or>', so we drop them
|
# Even-numbered tokens will be '<or>', so we drop them
|
||||||
disjunction.setParseAction(lambda _s, _l, t: ["<or>"] + t[1::2])
|
disjunction.setParseAction(lambda _s, _l, t: ["<or>"] + t[1::2])
|
||||||
|
|
||||||
expr = disjunction | unary | atom
|
expr = disjunction | nary | unary | atom
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
|
|
||||||
@ -80,10 +93,11 @@ def match(cmp_value, spec):
|
|||||||
"""Match a given value to a given spec DSL."""
|
"""Match a given value to a given spec DSL."""
|
||||||
expr = make_grammar()
|
expr = make_grammar()
|
||||||
try:
|
try:
|
||||||
ast = expr.parseString(spec)
|
tree = expr.parseString(spec)
|
||||||
except pyparsing.ParseException:
|
except pyparsing.ParseException:
|
||||||
ast = [spec]
|
tree = [spec]
|
||||||
if len(ast) == 1:
|
if len(tree) == 1:
|
||||||
return ast[0] == cmp_value
|
return tree[0] == cmp_value
|
||||||
op = op_methods[ast[0]]
|
|
||||||
return op(cmp_value, *ast[1:])
|
op = op_methods[tree[0]]
|
||||||
|
return op(cmp_value, *tree[1:])
|
||||||
|
@ -337,3 +337,86 @@ class SpecsMatcherTestCase(test_base.BaseTestCase):
|
|||||||
value='3.0',
|
value='3.0',
|
||||||
req='== 3.1',
|
req='== 3.1',
|
||||||
matches=False)
|
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