# Copyright (C) 2018 Avatao.com Innovative Learning Kft. # All Rights Reserved. See LICENSE file for details. from shlex import split from re import search from tfw.decorators.lazy_property import lazy_property class CommandsEqual: """ This class is useful for comparing executed commands with excepted commands (i.e. when triggering a state change when the correct command is executed). Note that in most cases you should test the changes caused by the commands instead of just checking command history (stuff can be done in countless ways and preparing for every single case is impossible). This should only be used when testing the changes would be very difficult, like when explaining stuff with cli tools and such. This class implicitly converts to bool, use it like if CommandsEqual(...): ... It tries detecting differing command parameter orders with similar semantics and provides fuzzy logic options. The rationale behind this is that a few false positives are better than only accepting a single version of a command (i.e. using ==). """ def __init__( self, command_1, command_2, fuzzyness=1, begin_similarly=True, include_patterns=None, exclude_patterns=None ): """ :param command_1: Compared command 1 :param command_2: Compared command 2 :param fuzzyness: float between 0 and 1. the percentage of arguments required to match between commands to result in True. i.e 1 means 100% - all arguments need to be present in both commands, while 0.75 would mean 75% - in case of 4 arguments 1 could differ between the commands. :param begin_similarly: bool, the first word of the commands must match :param include_patterns: list of regex patterns the commands must include :param exclude_patterns: list of regex patterns the commands must exclude """ self.command_1 = split(command_1) self.command_2 = split(command_2) self.fuzzyness = fuzzyness self.begin_similarly = begin_similarly self.include_patterns = include_patterns self.exclude_patterns = exclude_patterns def __bool__(self): if self.begin_similarly: if not self.beginnings_are_equal: return False if self.include_patterns is not None: if not self.commands_contain_include_patterns: return False if self.exclude_patterns is not None: if not self.commands_contain_no_exclude_patterns: return False print(self.similarity) return self.similarity >= self.fuzzyness @lazy_property def beginnings_are_equal(self): return self.command_1[0] == self.command_2[0] @lazy_property def commands_contain_include_patterns(self): return all(( self.contains_regex_patterns(self.command_1, self.include_patterns), self.contains_regex_patterns(self.command_2, self.include_patterns) )) @lazy_property def commands_contain_no_exclude_patterns(self): return all(( not self.contains_regex_patterns(self.command_1, self.exclude_patterns), not self.contains_regex_patterns(self.command_2, self.exclude_patterns) )) @staticmethod def contains_regex_patterns(command, regex_parts): command = ' '.join(command) for pattern in regex_parts: if not search(pattern, command): return False return True @lazy_property def similarity(self): parts_1 = set(self.command_1) parts_2 = set(self.command_2) difference = parts_1 - parts_2 deviance = len(difference) / len(max(parts_1, parts_2)) return 1 - deviance