# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABC, abstractmethod
from typing import Iterable, Iterator, overload, Sequence
from robot.utils import Matcher, normalize, NormalizedDict
[docs]
class TagPatterns(Sequence["TagPattern"]):
def __init__(self, patterns: Iterable[str] = ()):
self._patterns = tuple(TagPattern.from_string(p) for p in Tags(patterns))
@property
def is_constant(self):
return all(p.is_constant for p in self._patterns)
[docs]
def match(self, tags: Iterable[str]) -> bool:
if not self._patterns:
return False
tags = normalize_tags(tags)
return any(p.match(tags) for p in self._patterns)
def __contains__(self, tag: str) -> bool:
return self.match(tag)
def __len__(self) -> int:
return len(self._patterns)
def __iter__(self) -> Iterator["TagPattern"]:
return iter(self._patterns)
def __getitem__(self, index: int) -> "TagPattern":
return self._patterns[index]
def __str__(self) -> str:
patterns = ", ".join(str(pattern) for pattern in self)
return f"[{patterns}]"
[docs]
class TagPattern(ABC):
is_constant = False
[docs]
@classmethod
def from_string(cls, pattern: str) -> "TagPattern":
pattern = pattern.replace(" ", "")
if "NOT" in pattern:
must_match, *must_not_match = pattern.split("NOT")
return NotTagPattern(must_match, must_not_match)
if "OR" in pattern:
return OrTagPattern(pattern.split("OR"))
if "AND" in pattern or "&" in pattern:
return AndTagPattern(pattern.replace("&", "AND").split("AND"))
return SingleTagPattern(pattern)
[docs]
@abstractmethod
def match(self, tags: Iterable[str]) -> bool:
raise NotImplementedError
@abstractmethod
def __iter__(self) -> Iterator["TagPattern"]:
raise NotImplementedError
@abstractmethod
def __str__(self) -> str:
raise NotImplementedError
[docs]
class SingleTagPattern(TagPattern):
def __init__(self, pattern: str):
# Normalization is handled here, not in Matcher, for performance reasons.
# This way we can normalize tags only once.
self._matcher = Matcher(
normalize(pattern, ignore="_"),
caseless=False,
spaceless=False,
)
@property
def is_constant(self):
pattern = self._matcher.pattern
return not ("*" in pattern or "?" in pattern or "[" in pattern)
[docs]
def match(self, tags: Iterable[str]) -> bool:
tags = normalize_tags(tags)
return self._matcher.match_any(tags)
def __iter__(self) -> Iterator["TagPattern"]:
yield self
def __str__(self) -> str:
return self._matcher.pattern
def __bool__(self) -> bool:
return bool(self._matcher)
[docs]
class AndTagPattern(TagPattern):
def __init__(self, patterns: Iterable[str]):
self._patterns = tuple(TagPattern.from_string(p) for p in patterns)
[docs]
def match(self, tags: Iterable[str]) -> bool:
tags = normalize_tags(tags)
return all(p.match(tags) for p in self._patterns)
def __iter__(self) -> Iterator["TagPattern"]:
return iter(self._patterns)
def __str__(self) -> str:
return " AND ".join(str(pattern) for pattern in self)
[docs]
class OrTagPattern(TagPattern):
def __init__(self, patterns: Iterable[str]):
self._patterns = tuple(TagPattern.from_string(p) for p in patterns)
[docs]
def match(self, tags: Iterable[str]) -> bool:
tags = normalize_tags(tags)
return any(p.match(tags) for p in self._patterns)
def __iter__(self) -> Iterator["TagPattern"]:
return iter(self._patterns)
def __str__(self) -> str:
return " OR ".join(str(pattern) for pattern in self)
[docs]
class NotTagPattern(TagPattern):
def __init__(self, must_match: str, must_not_match: Iterable[str]):
self._first = TagPattern.from_string(must_match)
self._rest = OrTagPattern(must_not_match)
[docs]
def match(self, tags: Iterable[str]) -> bool:
tags = normalize_tags(tags)
if self._first and not self._first.match(tags):
return False
return not self._rest.match(tags)
def __iter__(self) -> Iterator["TagPattern"]:
yield self._first
yield from self._rest
def __str__(self) -> str:
return " NOT ".join(str(pattern) for pattern in self).lstrip()