# -*- coding -*- """ Provides some command utility functions. TODO: matcher that ignores empty lines and whitespace and has contains comparison """ from __future__ import absolute_import, print_function from hamcrest import assert_that, is_not, equal_to, contains_string # DISABLED: from behave4cmd.hamcrest_text import matches_regexp import codecs DEBUG = False # ----------------------------------------------------------------------------- # CLASS: TextProxy # ----------------------------------------------------------------------------- # class TextProxy(object): # """ # Simplifies conversion between (Unicode) string and its byte representation. # Provides a ValueObject to store a string or its byte representation. # Afterwards you can explicitly access both representations by using: # # EXAMPLE: # # .. testcode:: # # from behave4cmd.textutil import TextProxy # message = TextProxy("Hello world", encoding="UTF-8") # assert message.data == "Hello world" # -- RAW DATA access. # assert isinstance(message.text, basestring) # assert isinstance(message.bytes, bytes) # assert message == "Hello world" # assert len(message) == len(message.data) == 11 # """ # encoding_errors = "strict" # default_encoding = "UTF-8" # # def __init__(self, data=None, encoding=None, errors=None): # self.encoding = encoding or self.default_encoding # self.errors = errors or self.encoding_errors # self.set(data) # # def get(self): # return self.data # # def set(self, data): # self.data = data or "" # self._text = None # self._bytes = None # # def clear(self): # self.set(None) # # @property # def text(self): # """Provide access to string-representation of the data.""" # if self._text is None: # if isinstance(self.data, basestring): # _text = self.data # elif isinstance(self.data, bytes): # _text = codecs.decode(self.data, self.encoding, self.errors) # else: # _text = str(self.data) # self._text = _text # assert isinstance(self._text, basestring) # return self._text # # @property # def bytes(self): # """Provide access to byte-representation of the data.""" # if self._bytes is None: # if isinstance(self.data, bytes) and not isinstance(self.data, str): # self._bytes = self.data # else: # text = self.data # if not isinstance(text, basestring): # text = unicode(text) # assert isinstance(text, basestring) # self._bytes = codecs.encode(text, self.encoding, self.errors) # assert isinstance(self._bytes, bytes) # return self._bytes # # def __repr__(self): # """Textual representation of this object.""" # data = self.data or "" # prefix = "" # if isinstance(data, bytes) and not isinstance(data, basestring): # prefix= u"b" # # str(self.text) # # str(self.encoding) # # str(prefix) # # _ = u"<TextProxy data[size=%d]=x'x', encoding=x>" % len(self) # # _ = u"<TextProxy data[size=x]=%s'x', encoding=x>" % prefix # # _ = u"<TextProxy data[size=x]=x'%s', encoding=x>" % self.text # # _ = u"<TextProxy data[size=x]=x'x', encoding=%s>" % self.encoding # return u"<TextProxy data[size=%d]=%s'%s', encoding=%s>" %\ # (len(self), prefix, self.text, self.encoding) # # def __str__(self): # """Conversion into str() object.""" # return self.text # # def __bytes__(self): # """Conversion into bytes() object.""" # return self.bytes # # def __bool__(self): # """Conversion into a bool value, used for truth testing.""" # return bool(self.data) # # def __iter__(self): # """Conversion into an iterator.""" # return iter(self.data) # # def __contains__(self, item): # """Check if item is contained in raw data.""" # if isinstance(item, basestring): # return item in self.text # elif isinstance(item, bytes): # return item in self.bytes # else: # return item in self.data # # def __len__(self): # if self.data is None: # return 0 # return len(self.data) # # def __nonzero__(self): # return len(self) > 0 # # def __eq__(self, other): # if isinstance(other, basestring): # return self.text == other # elif isinstance(other, bytes): # return self.bytes == other # else: # return self.data == other # # def __ne__(self, other): # return not (self == other) # # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def template_substitute(text, **kwargs): """ Replace placeholders in text by using the data mapping. Other placeholders that is not represented by data is left untouched. :param text: Text to search and replace placeholders. :param data: Data mapping/dict for placeholder key and values. :return: Potentially modified text with replaced placeholders. """ for name, value in kwargs.items(): placeholder_pattern = "{%s}" % name if placeholder_pattern in text: text = text.replace(placeholder_pattern, value) return text def text_remove_empty_lines(text): """ Whitespace normalization: - Strip empty lines - Strip trailing whitespace """ lines = [ line.rstrip() for line in text.splitlines() if line.strip() ] return "\n".join(lines) def text_normalize(text): """ Whitespace normalization: - Strip empty lines - Strip leading whitespace in a line - Strip trailing whitespace in a line - Normalize line endings """ # if not isinstance(text, str): if isinstance(text, bytes): # -- MAYBE: command.ouput => bytes, encoded stream output. text = codecs.decode(text) lines = [ line.strip() for line in text.splitlines() if line.strip() ] return "\n".join(lines) # ----------------------------------------------------------------------------- # ASSERTIONS: # ----------------------------------------------------------------------------- from hamcrest.library.text.substringmatcher import SubstringMatcher from hamcrest.core.helpers.hasmethod import hasmethod class StringContainsMultipleTimes(SubstringMatcher): def __init__(self, substring, expected_count): super(StringContainsMultipleTimes, self).__init__(substring) self.expected_count = expected_count self.actual_count = 0 def describe_to(self, description): description.append_text('a string ') \ .append_text(self.relationship()) \ .append_text(" ") \ .append_description_of(self.substring) \ .append_text(" ") \ .append_description_of(self.expected_count) \ .append_text(" times instead of ") \ .append_description_of(self.actual_count) def _matches(self, item): if not hasmethod(item, "count"): return False self.actual_count = item.count(self.substring) return self.actual_count == self.expected_count def relationship(self): return "containing" def contains_substring_multiple_times(substring, expected_count): return StringContainsMultipleTimes(substring, expected_count) # ----------------------------------------------------------------------------- # ASSERTIONS: # ----------------------------------------------------------------------------- def assert_text_should_equal(actual_text, expected_text): assert_that(actual_text, equal_to(expected_text)) def assert_text_should_not_equal(actual_text, expected_text): assert_that(actual_text, is_not(equal_to(expected_text))) def assert_text_should_contain_exactly(text, expected_part): assert_that(text, contains_string(expected_part)) def assert_text_should_not_contain_exactly(text, expected_part): assert_that(text, is_not(contains_string(expected_part))) def assert_text_should_contain(text, expected_part): assert_that(text, contains_string(expected_part)) def assert_normtext_should_contain_multiple_times(text, expected_text, count): assert_that(text, contains_substring_multiple_times(expected_text, count)) def assert_text_should_not_contain(text, unexpected_part): assert_that(text, is_not(contains_string(unexpected_part))) def assert_normtext_should_equal(actual_text, expected_text): expected_text2 = text_normalize(expected_text.strip()) actual_text2 = text_normalize(actual_text.strip()) assert_that(actual_text2, equal_to(expected_text2)) def assert_normtext_should_not_equal(actual_text, expected_text): expected_text2 = text_normalize(expected_text.strip()) actual_text2 = text_normalize(actual_text.strip()) assert_that(actual_text2, is_not(equal_to(expected_text2))) def assert_normtext_should_contain(text, expected_part): expected_part2 = text_normalize(expected_part) actual_text = text_normalize(text.strip()) if DEBUG: print("expected:\n{0}".format(expected_part2)) print("actual:\n{0}".format(actual_text)) assert_text_should_contain(actual_text, expected_part2) def assert_normtext_should_not_contain(text, unexpected_part): unexpected_part2 = text_normalize(unexpected_part) actual_text = text_normalize(text.strip()) if DEBUG: print("expected:\n{0}".format(unexpected_part2)) print("actual:\n{0}".format(actual_text)) assert_text_should_not_contain(actual_text, unexpected_part2) # def assert_text_should_match_pattern(text, pattern): # """ # Assert that the :attr:`text` matches the regular expression :attr:`pattern`. # # :param text: Multi-line text (as string). # :param pattern: Regular expression pattern (as string, compiled regexp). # :raise: AssertionError, if text matches not the pattern. # """ # assert_that(text, matches_regexp(pattern)) # # def assert_text_should_not_match_pattern(text, pattern): # """ # Assert that the :attr:`text` matches not the regular expression # :attr:`pattern`. # # :param text: Multi-line text (as string). # :param pattern: Regular expression pattern (as string, compiled regexp). # :raise: AssertionError, if text matches the pattern. # """ # assert_that(text, is_not(matches_regexp(pattern))) # # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": import doctest doctest.testmod()