Beispiel #1
0
    def test(self, _filter: str, obj_or_dissection: Union[object, List[str]]) -> bool:
        """Tests the given filter against the dissection of an object.
        Returns whether the key:value pairs match logically. Case insensitive.
        
        That is, if all AND sections within any OR section evaluate to True, in the expanded filter."""
        if isinstance(obj_or_dissection, list):
            dissection = obj_or_dissection
        else:
            dissection = self.dissect(obj_or_dissection)

        _filter = _filter.lower()
        for or_split, _ in split_unescaped(expand(_filter), OR_GATES):
            
            passes_and = True
            for and_split, _ in split_unescaped(or_split, AND_GATES):
                without_not_gate, not_gate = extract_not(and_split)
                if not_gate:
                    if self.test_kvpair(without_not_gate, dissection):
                        passes_and = False
                        break
                else:
                    if not self.test_kvpair(and_split, dissection):
                        passes_and = False
                        break
            
            if passes_and:
                return True

        return False
Beispiel #2
0
def test_split_unescaped_special_quotes():
    generator = split_unescaped(
        "type:“One & two “x | y””&user:“some two or three”",
        ("&", "|", " or "))
    assert next(generator, None) == ("type:“One & two “x | y””", "&")
    assert next(generator, None) == ("user:“some two or three”", None)
    assert next(generator, None) is None
Beispiel #3
0
def test_split_unescaped():
    generator = split_unescaped(
        "type:\"One & two \"x | y\"\"&user:\"some two or three\"",
        ("&", "|", " or "))
    assert next(generator, None) == ("type:\"One & two \"x | y\"\"", "&")
    assert next(generator, None) == ("user:\"some two or three\"", None)
    assert next(generator, None) is None
Beispiel #4
0
def get_invalid_words(_filter: str) -> Generator[str, None, None]:
    """Returns all space-separated instances of text, which are neither key-value
    pairs nor logical gates, in the given filter."""
    for split, _ in split_unescaped(expand(_filter), (" ",)):
        is_key_value_pair = re.match(KEY_VALUE_PATTERN, split)
        is_logical_gate = split.lower() in map(lambda gate: gate.replace(" ", ""), NOT_GATES + AND_GATES + OR_GATES)
        if not is_key_value_pair and not is_logical_gate:
            yield split
Beispiel #5
0
def parse_command(content: str, context: Message=None, client: Client=None) -> Command:
    """Returns the given content string as a command, if it's formatted as one, else None.
    Optionally with the given message as context."""
    match = regex.search(r"^" + regex.escape(get_prefix(context)) + r"([A-Za-z]+) ?(.+)?", content)
    if match:
        name = match.group(1)
        args = match.group(2)

        if args:
            args_respecting_quotes = [unescape(arg) for arg, _ in split_unescaped(args, (" ",))]
            return Command(name, *args_respecting_quotes, context=context, client=client)
        return Command(name, context=context, client=client)
    return None
Beispiel #6
0
def test_split_unescaped_cache_timing():
    iterations = 10000

    time = datetime.utcnow()
    generator = split_unescaped(
        "type:\"one and two\" and user:three and " * iterations, (" and ", ))
    for i in range(iterations - 1):
        assert next(generator, None) == ("type:\"one and two\"", " and ")
        assert next(generator, None) == ("user:three", " and ")
    delta_time_uncached = datetime.utcnow() - time

    time = datetime.utcnow()
    generator = split_unescaped(
        "type:\"one and two\" and user:three and " * iterations, (" and ", ))
    for i in range(iterations - 1):
        assert next(generator, None) == ("type:\"one and two\"", " and ")
        assert next(generator, None) == ("user:three", " and ")
    delta_time_cached = datetime.utcnow() - time

    # Retrieving from cache should be approximately 10 times faster.
    assert delta_time_uncached.total_seconds(
    ) / delta_time_cached.total_seconds() > 10
Beispiel #7
0
def get_missing_gate(_filter: str) -> Generator[str, None, None]:
    """Returns a tuple of the first two space-separated instances of text,
    which have no gate between them, in the given filter."""
    was_gate = False
    prev_split = None
    for split, _ in split_unescaped(expand(_filter), (" ",)):
        is_key_value_pair = re.match(KEY_VALUE_PATTERN, split)
        is_logical_gate = split.lower() in map(lambda gate: gate.replace(" ", ""), NOT_GATES + AND_GATES + OR_GATES)

        if prev_split and is_key_value_pair and not was_gate:
            return (prev_split, split)
        
        was_gate = is_logical_gate
        prev_split = split
Beispiel #8
0
def filter_to_sql(_filter: str) -> Tuple[str, tuple]:
    """Returns a tuple of the filter converted to an SQL WHERE clause and the inputs to the
    WHERE clause (e.g. ("type=%s", ("nominate",)) ), for use with the scraper database."""
    if not _filter:
        # Without a filter, we simply let everything through.
        return ("TRUE", ())

    if not is_valid(_filter, filter_context):
        raise ValueError("Received an invalid filter; cannot convert to sql.")

    converted_words = []
    converted_values = []
    for word, _ in split_unescaped(expand(_filter), (" ",)):
        # Convert gate symbols in the filter (e.g. "&", "!", "and", "|") to "AND", "OR", and "NOT".
        if any(map(lambda gate: word.lower() == gate.strip().lower(), AND_GATES)): word = "AND"
        if any(map(lambda gate: word.lower() == gate.strip().lower(), OR_GATES)):  word = "OR"
        if any(map(lambda gate: word.lower() == gate.strip().lower(), NOT_GATES)): word = "NOT"
        if word in ["AND", "OR", "NOT"]:
            converted_words.append(word)
            continue

        key, value = next(get_key_value_pairs(word))
        tag = filter_context.get_tag(key)
        if not tag:
            continue

        values = []

        # Support type aliases (e.g. "resolve" should be converted to "issue-resolve").
        if key.lower() == "type":
            for _type in TYPE_ALIASES:
                if value.lower() in TYPE_ALIASES[_type]:
                    values.append(_type)
        
        # Support group aliases (e.g. "nat" should be converted to "7").
        if key.lower() == "group":
            for group in GROUP_ALIASES:
                if value.lower() in GROUP_ALIASES[group]:
                    values.append(group)

        if not values:
            # Our value is not an alias, so we can use it directly.
            values.append(value)

        if len(values) > 1: converted_words.append("(" + " OR ".join([TAG_TO_SQL[tag]] * len(values)) + ")")
        else:               converted_words.append(TAG_TO_SQL[tag])
        converted_values.extend(values)
    
    return (" ".join(converted_words), tuple(converted_values))
Beispiel #9
0
def test_split_unescaped_long_delimiter():
    generator = split_unescaped("type:\"one and two\" and user:three",
                                (" and ", ))
    assert next(generator, None) == ("type:\"one and two\"", " and ")
    assert next(generator, None) == ("user:three", None)
    assert next(generator, None) is None