예제 #1
0
def test_expand_inf_2():
    with pytest.raises(ValueError) as err:
        expand(
            "content:(\"% 1/7,%\" or \"1/7,%\") and not content:(\"% 1/7\" or \"% 1/7 %\" or \"1/7 %\" or \"1/7\") "
            "and not type:(qualify or nominate)")

    assert "expanded infinitely" in str(err)
예제 #2
0
def test_expand_inf():
    with pytest.raises(ValueError) as err:
        expand(
            "type:(rank and love and qualify and disqualify and remove_from_loved) mode:(osu and taiko and catch and mania)"
        )

    assert "expanded infinitely" in str(err)
예제 #3
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
예제 #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
예제 #5
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
예제 #6
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))
예제 #7
0
def get_invalid_gates(_filter: str) -> Generator[str, None, None]:
    """Returns a generator of invalid gates from the given filter (i.e. starting or ending the filter with one)."""
    expanded_filter = expand(_filter).strip()
    for gate in map(lambda g: g.strip(), (AND_GATES + OR_GATES)):
        if expanded_filter.startswith(gate) or expanded_filter.endswith(gate):
            yield gate
예제 #8
0
def get_key_value_pairs(_filter: str) -> Generator[Tuple[str, str], None, None]:
    """Returns a generator of key-value pair tuples from the given filter."""
    expansion = expand(_filter)
    for match in re.finditer(KEY_VALUE_PATTERN, expansion):
        yield (match.group(1), match.group(2).strip("\""))
예제 #9
0
def test_expand_redundancy():
    assert expand("(type:(qualify))") == "type:qualify"
예제 #10
0
def test_expand_complex_capital():
    assert expand("A OR E AND NOT (B AND (C OR NOT D))"
                  ) == "A or E and not B or E and not C and E and D"
예제 #11
0
def test_expand_complex():
    assert expand("A or E and not (B and (C or not D))"
                  ) == "A or E and not B or E and not C and E and D"
예제 #12
0
def test_expand_not_before_type():
    assert expand("not type:(nominate or qualify)"
                  ) == "not type:nominate and not type:qualify"
예제 #13
0
def test_expand_not():
    assert expand("type:not (nominate or qualify)"
                  ) == "not type:nominate and not type:qualify"
예제 #14
0
def test_expand_multiple_and():
    assert (
        expand("type:(nominate or qualify) and user:(123 or 456)") ==
        "type:nominate and user:123 or type:nominate and user:456 or type:qualify and user:123 or type:qualify and user:456"
    )
예제 #15
0
def test_expand_and_quotes():
    assert (expand(
        "type:(nominate or qualify) and user:\"some user with and in their name\""
    ) == "type:nominate and user:\"some user with and in their name\" or type:qualify and user:\"some user with and in their name\""
            )
예제 #16
0
def test_expand_and():
    assert expand(
        "type:(nominate or qualify) and user:123"
    ) == "type:nominate and user:123 or type:qualify and user:123"
예제 #17
0
def test_expand_nested_leading_or():
    assert expand("type:(not B or (not C and D))"
                  ) == "not type:B or not type:C and type:D"
예제 #18
0
def test_expand():
    assert expand(
        "type:(nominate or qualify)") == "type:nominate or type:qualify"