baseimage-tutorial-framework/tfw/components/terminal/commands_equal.py

108 lines
3.9 KiB
Python
Raw Permalink Normal View History

from shlex import split
from re import search
from tfw.internals.lazy import lazy_property
2018-08-03 13:01:44 +00:00
class CommandsEqual:
2018-08-06 13:52:01 +00:00
# pylint: disable=too-many-arguments
2018-08-04 19:49:06 +00:00
"""
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
):
2018-08-04 19:49:06 +00:00
"""
: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
"""
2018-08-03 13:01:44 +00:00
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
2018-08-03 13:01:44 +00:00
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
2018-08-03 13:01:44 +00:00
@lazy_property
def similarity(self):
parts_1 = set(self.command_1)
parts_2 = set(self.command_2)
2018-08-03 13:01:44 +00:00
difference = parts_1 - parts_2
deviance = len(difference) / len(max(parts_1, parts_2))
return 1 - deviance