From 16629d108e574b9d66ba8e2b823ec2351e5311e0 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Fri, 5 Aug 2011 15:38:41 +0200 Subject: [PATCH] Implement globbing operator for filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operators β=*β and β!*β do globbing in filters, e.g.: $ gnt-instance list --no-headers -o name 'name =* "*.site"' inst1.site.example.com Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/qlang.py | 19 +++++++++++++++++-- man/ganeti.rst | 7 +++++++ test/ganeti.qlang_unittest.py | 6 ++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/qlang.py b/lib/qlang.py index 5167d773a..0c5169e03 100644 --- a/lib/qlang.py +++ b/lib/qlang.py @@ -38,6 +38,7 @@ import pyparsing as pyp from ganeti import errors from ganeti import netutils +from ganeti import utils # Logic operators with one or more operands, each of which is a filter on its @@ -146,8 +147,10 @@ def BuildFilterParser(): number = pyp.Combine(pyp.Optional(num_sign) + pyp.Word(pyp.nums)) number.setParseAction(lambda toks: int(toks[0])) + quoted_string = pyp.quotedString.copy().setParseAction(pyp.removeQuotes) + # Right-hand-side value - rval = (number | pyp.quotedString.setParseAction(pyp.removeQuotes)) + rval = (number | quoted_string) # Boolean condition bool_cond = field_name.copy() @@ -184,10 +187,22 @@ def BuildFilterParser(): not_regexp_cond.setParseAction(lambda (field, value): [[OP_NOT, [OP_REGEXP, field, value]]]) + # Globbing, e.g. name =* "*.site" + glob_cond = (field_name + pyp.Suppress("=*") + quoted_string) + glob_cond.setParseAction(lambda (field, value): + [[OP_REGEXP, field, + utils.DnsNameGlobPattern(value)]]) + + not_glob_cond = (field_name + pyp.Suppress("!*") + quoted_string) + not_glob_cond.setParseAction(lambda (field, value): + [[OP_NOT, [OP_REGEXP, field, + utils.DnsNameGlobPattern(value)]]]) + # All possible conditions condition = (binary_cond ^ bool_cond ^ in_cond ^ not_in_cond ^ - regexp_cond ^ not_regexp_cond) + regexp_cond ^ not_regexp_cond ^ + glob_cond ^ not_glob_cond) # Associativity operators filter_expr = pyp.operatorPrecedence(condition, [ diff --git a/man/ganeti.rst b/man/ganeti.rst index 9b31845d7..2fdcb4481 100644 --- a/man/ganeti.rst +++ b/man/ganeti.rst @@ -265,6 +265,9 @@ Syntax in pseudo-BNF:: */ | <field> { =~ | !~ } m/<re>/<re-modifiers> + /* Globbing */ + | <field> { =* | !* } <quoted-string> + /* Boolean */ | <field> } @@ -283,6 +286,10 @@ Operators: Pattern match using regular expression *!~* Logically negated from *=~* +*=\** + Globbing, see **glob**(7), though only * and ? are supported +*!\** + Logically negated from *=\** *in*, *not in* Collection membership and negation diff --git a/test/ganeti.qlang_unittest.py b/test/ganeti.qlang_unittest.py index 74100d216..ed8f77f93 100755 --- a/test/ganeti.qlang_unittest.py +++ b/test/ganeti.qlang_unittest.py @@ -147,6 +147,12 @@ class TestParseFilter(unittest.TestCase): self._Test("notname =~ m%stest%s" % (i, i), [qlang.OP_REGEXP, "notname", "test"]) + self._Test("name =* '*.site'", + [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.site")]) + self._Test("field !* '*.example.*'", + [qlang.OP_NOT, [qlang.OP_REGEXP, "field", + utils.DnsNameGlobPattern("*.example.*")]]) + def testAllFields(self): for name in frozenset(i for d in query.ALL_FIELD_LISTS for i in d.keys()): self._Test("%s == \"value\"" % name, [qlang.OP_EQUAL, name, "value"]) -- GitLab