Source code for insights.core.taglang

"""
Simple language for defining predicates against a list or set of strings.

Operator Precedence:
    - ``!`` high - opposite truth value of its predicate
    - ``/`` high - starts a regex that continues until whitespace unless quoted
    - ``&`` medium - "and" of two predicates
    - ``|`` low - "or" of two predicates
    - ``,`` low - "or" of two predicates. Synonym for ``|``.

It supports grouping with parentheses and quoted strings/regexes surrounded
with either single or double quotes.

Examples:
    >>> pred = parse("a | b & !c")  # means (a or (b and (not c)))
    >>> pred(["a"])
    True
    >>> pred(["b"])
    True
    >>> pred(["b", "c"])
    False
    >>> pred = parse("/net | apache")
    >>> pred(["networking"])
    True
    >>> pred(["mynetwork"])
    True
    >>> pred(["apache"])
    True
    >>> pred(["security"])
    False
    >>> pred = parse("(a | b) & c")
    >>> pred(["a", "c"])
    True
    >>> pred(["b", "c"])
    True
    >>> pred(["a"])
    False
    >>> pred(["b"])
    False
    >>> pred(["c"])
    False

Regular expressions start with a forward slash ``/`` and continue until
whitespace unless they are quoted with either single or double quotes. This
means that they can consume what would normally be considered an operator or a
closing parenthesis if you aren't careful.

For example, this is a parse error because the regex consumes the comma:
    >>> pred = parse("/net, apache")
    Exception

Instead, do this:
    >>> pred = parse("/net , apache")

or this:
    >>> pred = parse("/net | apache")

or this:
    >>> pred = parse("'/net', apache")
"""
import re
import string
from insights.parsr import (Char, EOF, Forward, InSet, Many, Opt, String,
                            QuotedString, WS)


[docs] class Predicate(object): """ Provides __call__ for invoking the Predicate like a function without having to explictly call its test method. """ def __call__(self, value): return self.test(value)
[docs] class Eq(Predicate): """ The value must be in the set of values. """ def __init__(self, value): self.value = value
[docs] def test(self, values): return self.value in values
[docs] class Regex(Predicate): """ The regex must match at least one of the values. """ def __init__(self, value): self.regex = re.compile(value)
[docs] def test(self, values): return any(self.regex.search(v) for v in values)
[docs] class Not(Predicate): """ The values must not satisfy the wrapped condition. """ def __init__(self, pred): self.pred = pred
[docs] def test(self, value): return not self.pred.test(value)
[docs] class And(Predicate): """ The values must satisfy both the left and the right condition. """ def __init__(self, left, right): self.left = left self.right = right
[docs] def test(self, value): return self.left.test(value) and self.right.test(value)
[docs] class Or(Predicate): """ The values must satisfy either the left or the right condition. """ def __init__(self, left, right): self.left = left self.right = right
[docs] def test(self, value): return self.left.test(value) or self.right.test(value)
[docs] def negate(args): op, p = args return Not(p) if op else p
[docs] def oper(args): left, rest = args for op, right in rest: if op == "&": left = And(left, right) if op in ",|": left = Or(left, right) return left
expr = Forward() bare = String(set(string.printable) - (set(string.whitespace) | set(")&,|"))) tag = (QuotedString | bare).map(Eq) regex_body = QuotedString | String(set(string.printable) - set(string.whitespace)) regex = Char("/") >> regex_body.map(Regex) factor_body = ((Char("(") >> expr << Char(")")) | regex | tag) factor = (WS >> Opt(Char("!")) + factor_body << WS).map(negate) term = (factor + Many(Char("&") + factor)).map(oper) expr <= (term + Many(InSet(",|") + term)).map(oper) parse = expr << EOF