From b9d8669a030f1e44b7761f04f8ff8f717fb35ef9 Mon Sep 17 00:00:00 2001 From: jshwi Date: Sat, 1 Feb 2020 14:39:57 +1100 Subject: [PATCH] [feat] Make some methods & variables public for more manual configuration --- conftest.py | 95 ++++++++---- object_colors.py | 324 ++++++++++++++++++++++----------------- requirements.txt | 26 +++- tests/_test.py | 117 +++++--------- tests/get_key_test.py | 10 +- tests/get_test.py | 12 ++ tests/multicolor_test.py | 29 +++- tests/pop_test.py | 18 +++ 8 files changed, 377 insertions(+), 254 deletions(-) create mode 100644 tests/get_test.py create mode 100644 tests/pop_test.py diff --git a/conftest.py b/conftest.py index 85bcf220..675c063c 100644 --- a/conftest.py +++ b/conftest.py @@ -1,48 +1,50 @@ #!/usr/bin/env python3 +from typing import List + from pytest import fixture from object_colors import Color @fixture -def color(): +def color() -> Color: populated = Color() populated.populate_colors() return populated @fixture -def color_str(color, str_): +def color_str(color: Color, str_: str) -> Color: return color.green.get(str_) @fixture -def str_(): +def str_() -> str: return "Cc: My Business ;" @fixture -def marked(red, reset): +def marked(red: str, reset: str) -> str: return f"{red}Cc:{reset} My Business ;" @fixture -def marked_color(green, red, reset): +def marked_color(green: str, red: str, reset: str) -> str: return f"{green}{red}Cc:{green} My Business ;{reset}" @fixture -def marked_second(red, reset): +def marked_second(red: str, reset: str) -> str: return f"Cc: {red}My{reset} Business ;" @fixture -def marked_second_color(green, red, reset): +def marked_second_color(green: str, red: str, reset: str) -> str: return f"{green}Cc: {red}My{green} Business ;{reset}" @fixture -def all_cs(red, reset): +def all_cs(red: str, reset: str) -> str: return ( f"{red}C{reset}{red}c{reset}{red}:{reset} My " f"Business ;" @@ -50,7 +52,7 @@ def all_cs(red, reset): @fixture -def dupe_marked_color(green, red, reset): +def dupe_marked_color(green: str, red: str, reset: str) -> str: """do dupe fixture but with ignore""" return ( f"{green}This is a string that says {red}one{green} several times. " @@ -61,7 +63,7 @@ def dupe_marked_color(green, red, reset): @fixture -def dupe_marked(green, red, reset): +def dupe_marked(green: str, red: str, reset: str) -> str: """do dupe fixture but with ignore""" return ( f"This is a string that says {red}one{reset} several times. It says " @@ -72,7 +74,7 @@ def dupe_marked(green, red, reset): @fixture -def all_cs_color(red, green, reset): +def all_cs_color(red: str, green: str, reset: str) -> str: return ( f"{green}{red}C{green}{red}c{green}{red}:{green} My " f"Business ;{reset}" @@ -80,7 +82,7 @@ def all_cs_color(red, green, reset): @fixture -def all_cs_no_caps(red, reset): +def all_cs_no_caps(red: str, reset: str) -> str: return ( f"C{red}c{reset}{red}:{reset} My " f"Business ;" @@ -88,7 +90,7 @@ def all_cs_no_caps(red, reset): @fixture -def all_cs_no_caps_color(red, green, reset): +def all_cs_no_caps_color(red: str, green: str, reset: str) -> str: return ( f"{green}C{red}c{green}{red}:{green} My " f"Business ;{reset}" @@ -96,7 +98,7 @@ def all_cs_no_caps_color(red, green, reset): @fixture -def exact_idx(red, reset): +def exact_idx(red: str, reset: str) -> str: return ( f"{red}C{reset}{red}c{reset}: My Business " f";" @@ -104,7 +106,7 @@ def exact_idx(red, reset): @fixture -def exact_idx_color(green, red, reset): +def exact_idx_color(green: str, red: str, reset: str) -> str: return ( f"{green}{red}C{green}{red}c{green}: My Business " f";{reset}" @@ -112,7 +114,7 @@ def exact_idx_color(green, red, reset): @fixture -def dupes(): +def dupes() -> str: return ( "This is a string that says one several times. It says one in this " "sentence. And one in this sentence. This sentence also has one in " @@ -121,7 +123,7 @@ def dupes(): @fixture -def colored_dupes(): +def colored_dupes() -> str: return ( "\u001b[0;32;40mThis is a string that says one several times. It says " "one in this sentence. And one in this sentence. This sentence also " @@ -130,7 +132,7 @@ def colored_dupes(): @fixture -def scatter_cs(green, red, reset): +def scatter_cs(green: str, red: str, reset: str) -> str: return ( f"{red}C{reset}{red}c{reset}{red}:{reset} My Business " f";" @@ -138,7 +140,7 @@ def scatter_cs(green, red, reset): @fixture -def scatter_cs_color(green, red, reset): +def scatter_cs_color(green: str, red: str, reset: str) -> str: return ( f"{green}{red}C{green}{red}c{green}{red}:{green} My Business " f";{reset}" @@ -146,12 +148,12 @@ def scatter_cs_color(green, red, reset): @fixture -def scatter_cs_exact(green, red, reset): +def scatter_cs_exact(green: str, red: str, reset: str) -> str: return f"C{red}c{reset}: My Business ;" @fixture -def scatter_cs_exact_color(green, red, reset): +def scatter_cs_exact_color(green: str, red: str, reset: str) -> str: return ( f"{green}C{red}c{green}: My Business " f";{reset}" @@ -159,12 +161,12 @@ def scatter_cs_exact_color(green, red, reset): @fixture -def spaced_words(green, red, reset): +def spaced_words(green: str, red: str, reset: str) -> str: return f"{red}Cc:{reset} My {red}Business{reset} ;" @fixture -def spaced_words_color(green, red, reset): +def spaced_words_color(green: str, red: str, reset: str) -> str: return ( f"{green}{red}Cc:{green} My {red}Business{green} " f";{reset}" @@ -172,20 +174,59 @@ def spaced_words_color(green, red, reset): @fixture -def green(): +def green() -> str: return "\u001b[0;32;40m" @fixture -def red(): +def red() -> str: return "\u001b[0;31;40m" @fixture -def reset(): +def reset() -> str: return "\u001b[0;0m" @fixture -def color_keys(color, str_): +def color_keys(color: Color, str_: str) -> str: return color.red.get_key(str_, "c", ignore_case=True, scatter=True) + + +@fixture +def attrs() -> List[str]: + return [ + "get", + "set", + "get", + "get_key", + "print", + "print_key", + "pop", + "multicolor", + "populate_colors" + ] + + +@fixture +def colors(): + return [ + "black", + "red", + "green", + "yellow", + "blue", + "purple", + "cyan", + "white", + ] + + +@fixture +def instances(colors) -> List[str]: + return ["text", "effect", "background", "bold"] + colors + + +@fixture +def for_multiple_colors(): + return "Testing for multiple colors" diff --git a/object_colors.py b/object_colors.py index f7a4a206..988ded0f 100644 --- a/object_colors.py +++ b/object_colors.py @@ -9,7 +9,7 @@ import re from random import randint -from typing import Union, Any, Optional, Tuple, Dict, List, Pattern +from typing import Union, Any, Optional, Tuple, Dict, List class Color: @@ -100,8 +100,6 @@ class Color: """ - __code = "\u001b" - __reset = f"{__code}[0;0m" __keys = ["text", "effect", "background"] __opts = { "colors": [ @@ -117,6 +115,10 @@ class Color: "effect": ["none", "bold", "bright", "underline", "negative"], } + code = "\u001b" + reset = f"{code}[0;0m" + ansi_escape = re.compile(r"(\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))") + def __init__(self, *args: Union[str, int], **kwargs: Any) -> None: self.text = 7 self.effect = 0 @@ -153,7 +155,7 @@ def __get_opts(key: str) -> List[str]: return Color.__opts["colors"] @staticmethod - def __str_obj(pos: List[int]) -> Dict[str, Union[str, int, bool]]: + def __str_object(pos: List[int]) -> Dict[str, Union[str, int, bool]]: # dictionary of values to resolve index of substrings to color return { "freeze": 0, @@ -199,7 +201,7 @@ def __get_processed( # check whether keywords are good to go or need to be resolved # first for index_, key in enumerate(Color.__keys): - kwargs = self.__within_range(index_, args, kwargs, key) + kwargs = self.__kwargs_in_range(index_, args, kwargs, key) kwargs = self.__resolve_kwargs(key, kwargs) return kwargs @@ -224,7 +226,7 @@ def __make_subclass( return True return False - def __get_bold_obj(self) -> Dict[str, Dict[str, Union[str, Any]]]: + def __get_bold_object(self) -> Dict[str, Dict[str, Union[str, Any]]]: # return dictionary containing values necessary to convert # non-bold instance into corresponding bold instance and # vice-versa @@ -240,7 +242,7 @@ def __set_bold_attr(self) -> None: # Instantiate bold class object if bold is not set for more # flexible usage and less setting up when using this module to # manipulate particular colored strings - kwargs = self.__get_bold_obj() + kwargs = self.__get_bold_object() self.__make_subclass((), kwargs) def __bold_switch(self) -> None: @@ -250,28 +252,28 @@ def __bold_switch(self) -> None: if self.effect != 1: self.__set_bold_attr() - def __color_tuple( + def __get_colored_tuple( self, args: Tuple[Union[str, int]], reset: str ) -> Tuple[Union[str, int]]: args = list(args) # replace tuples containing strings with corresponding colored # strings for count, arg in enumerate(args): - args[count] = self.__color_str(arg, reset) + args[count] = self.__get_colored_str(arg, reset) return tuple(args) - def __color_setting(self) -> str: + def __color_settings(self) -> str: # get the colored string with ansi-escape code settings added - return f"{Color.__code}[{self.effect};3{self.text};4{self.background}m" + return f"{Color.code}[{self.effect};3{self.text};4{self.background}m" - def __color_str(self, str_: str, reset: str) -> str: + def __get_colored_str(self, str_: str, reset: str) -> str: # set desired reset code and return colored string reset = reset if reset else "" - setting = self.__color_setting() + setting = self.__color_settings() return f"{setting}{str_}{reset}" @staticmethod - def __add_swapped_index( + def __get_swapped_index( switches: Dict[str, bool], letter: str, idx: List[str] ) -> List[str]: # add additional swapped case of string index for scatter mode @@ -290,7 +292,7 @@ def __get_str_indices( key = list(key) for letter in key: idx.append(letter) - idx = self.__add_swapped_index(switches, letter, idx) + idx = self.__get_swapped_index(switches, letter, idx) return list(dict.fromkeys(idx)) @staticmethod @@ -305,50 +307,35 @@ def __extract_codes( helper.update({key: int(word[value])}) return helper - def __populate_state_obj( + def __populate_state_object( self, word: str, - state: Dict[str, Union[str, Dict[str, str]]], - ansi_esc: Pattern[str], + name_: str, + state: Dict[str, Union[Dict[str, str], List[str]]], ) -> Dict[str, Union[str, Dict[str, str]]]: # separate ansi codes from string and return a dict of both to # be used individually later - if ansi_esc.match(word): - if word != Color.__reset: - state["helper"] = self.__extract_codes(word, {}) + if Color.ansi_escape.match(word): + if word != Color.reset: + codes = self.__extract_codes(word, {}) + state[name_].update(codes) else: - state.update({"string": word}) + state["str_"].append(word) return state - @staticmethod - def __separate_ansi(str_: str, ansi_escape: Pattern[str]) -> List[str]: - # split string up by ansi escape codes and return list of all - # sections of the string - words = re.split(ansi_escape, str_) - return [word for word in words if word != ""] - - def __get_state_obj( - self, words: List[str], ansi_escape: Pattern[str] + def __get_state_object( + self, words: List[str], name_: str ) -> Dict[str, Union[str, Dict[str, str]]]: - state = {"helper": {}} # iterate string split up by ansi escape codes to populate # object to organise them + state = {name_: {}, "str_": []} for word in words: - state = self.__populate_state_obj(word, state, ansi_escape) + state = self.__populate_state_object(word, name_, state) + state["str_"] = "".join(state["str_"]) return state - def __make_state_obj( - self, str_: str - ) -> Dict[str, Union[str, Dict[str, str]]]: - # split ansi codes and string and populate the object - # representing the string and its color before they were - # separated to restore its state later - ansi_escape = re.compile(r"(\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))") - words = self.__separate_ansi(str_, ansi_escape) - return self.__get_state_obj(words, ansi_escape) - @staticmethod - def __within_range( + def __kwargs_in_range( index_: int, args: Tuple[Any], kwargs: Dict[str, int], key: str ) -> Dict[str, int]: # the index is good to use if the value is not None and is less @@ -358,7 +345,7 @@ def __within_range( return kwargs @staticmethod - def __unusable_keywords( + def __keywords_not_ready( key: str, kwargs: Dict[str, Any], opts: List[str] ) -> bool: # determine whether keyword arguments provided aren't valid @@ -370,7 +357,7 @@ def __unusable_keywords( return True @staticmethod - def __alternate_opts( + def __resolve_alternate_opts( key: str, kwargs: Dict[str, Union[str, int]], opts: List[str], @@ -392,20 +379,10 @@ def __resolve_kwargs( # which convert kwargs from alternative values to integer codes default = 7 if key == "text" else 0 opts = self.__get_opts(key) - if self.__unusable_keywords(key, kwargs, opts): - kwargs = self.__alternate_opts(key, kwargs, opts, default) + if self.__keywords_not_ready(key, kwargs, opts): + kwargs = self.__resolve_alternate_opts(key, kwargs, opts, default) return kwargs - def __instantiate_state(self, str_: str) -> str: - # separate ansi code from string, creating a Color subclass from - # the pre-existing color, available in self, and return the - # bare string - state = self.__make_state_obj(str_) - if state["string"]: - str_ = state.pop("string") - self.__make_subclass((), state) - return str_ - def __resolve_ansi_code( self, keys: Tuple[str], str_: str, switches: Dict[str, bool] ) -> str: @@ -416,9 +393,9 @@ def __resolve_ansi_code( # color the keywords entered as *args # surround the string with its original color # remove the helper function and return colored string - str_ = self.__instantiate_state(str_) + str_ = self.set_str(str_) reset = self.helper.get("", reset=None) - str_ = self.__manipulate_string(keys, str_, reset, switches) + str_ = self.__process_str(keys, str_, reset, switches) str_ = self.helper.get(str_) del self.__dict__["helper"] return str_ @@ -447,7 +424,7 @@ def __iterate_ours( pos = self.__iterate_theirs(letter, ours, pos, count) return pos - def __scatter_pos( + def __get_scattered_positions( self, str_: str, idx: List[str], pos: List[int] ) -> List[int]: # get the scattered position of searched keys within string @@ -456,7 +433,7 @@ def __scatter_pos( return pos @staticmethod - def __normalize_strings( + def __normalize_strs( key: str, str_: str, switches: Dict[str, bool] ) -> Dict[str, str]: # ignore case of the searched string by searching lowercase @@ -467,7 +444,9 @@ def __normalize_strings( return strs @staticmethod - def __add_multiple_pos(key: str, str_: str, pos: List[int]) -> List[int]: + def __get_multiple_positions( + key: str, str_: str, pos: List[int] + ) -> List[int]: # generator will be needed if there are multiple positions of a # single substring places = [p for p in range(len(str_)) if str_.find(key, p) == p] @@ -475,33 +454,43 @@ def __add_multiple_pos(key: str, str_: str, pos: List[int]) -> List[int]: pos.append(place) return pos - def __find_key(self, strs: Dict[str, str], pos: List[int]) -> List[int]: + def __find_key_positions( + self, strs: Dict[str, str], pos: List[int] + ) -> List[int]: # find the position of keywords(s) and return list of results key = strs["key"] str_ = strs["str_"] if f" {key} " in f" {str_} ": - pos = self.__add_multiple_pos(key, str_, pos) + pos = self.__get_multiple_positions(key, str_, pos) return pos def __get_key_positions( - self, str_: str, key: str, pos: List[int], switches: Dict[str, bool] + self, + str_: str, + key: str, + pos: List[int], + switches: Dict[str, bool] ) -> List[int]: # get position(s) of searched term as an integer within string - strs = self.__normalize_strings(key, str_, switches) - return self.__find_key(strs, pos) + strs = self.__normalize_strs(key, str_, switches) + return self.__find_key_positions(strs, pos) - def __mode_position( - self, key: str, str_: str, pos: List[int], switches: Dict[str, bool] + def __normalize_position( + self, + key: str, + str_: str, + pos: List[int], + switches: Dict[str, bool] ) -> List[int]: # return list of search positions for scattered keys if # `scatter` is True or return colored full words if False if switches["any"]: idx = self.__get_str_indices(key, switches) - return self.__scatter_pos(str_, idx, pos) + return self.__get_scattered_positions(str_, idx, pos) return self.__get_key_positions(str_, key, pos, switches) @staticmethod - def __update_str_obj( + def __update_str_object( freeze: int, letter: str, obj: Dict[str, Union[int, str, bool, List[str]]] @@ -512,7 +501,7 @@ def __update_str_obj( obj["pos"].pop(0) return obj - def __color_index( + def __process_index( self, obj: Dict[str, Union[str, int, bool, List[str]]], str_: str, @@ -523,7 +512,7 @@ def __color_index( obj["letter"] = str_[count] letter = self.get(obj["letter"], reset=None) if obj["pos"] and count == obj["pos"][0]: - obj = self.__update_str_obj(freeze, letter, obj) + obj = self.__update_str_object(freeze, letter, obj) return obj @staticmethod @@ -543,20 +532,20 @@ def __reset_index( obj.update({"letter": obj["letter"] + reset, "applied": False}) return obj - def __color_keys( + def __process_keys( self, str_: str, pos: List[int], reset: str, len_key: int ) -> str: # color the searched keys bases on their index within the string compile_ = [] - obj = self.__str_obj(pos) + obj = self.__str_object(pos) for count, _ in enumerate(str_): - obj = self.__color_index(obj, str_, count) + obj = self.__process_index(obj, str_, count) obj = self.__reset_index(obj, reset, len_key, count) letter = obj["letter"] compile_.append(letter) return "".join(compile_) - def __manipulate_string( + def __process_str( self, keys: Tuple[str], str_: str, @@ -566,13 +555,13 @@ def __manipulate_string( # process arguments provided to color the string accordingly pos = [] for key in keys: - pos = self.__mode_position(key, str_, pos, switches) + pos = self.__normalize_position(key, str_, pos, switches) len_key = len(key) if not switches["any"] else 1 - str_ = self.__color_keys(str_, pos, reset, len_key) + str_ = self.__process_keys(str_, pos, reset, len_key) return str_ @staticmethod - def __is_class( + def __collect_values( value: Union[int, Color], key: str, colors: Dict[str, List[Union[Color, int]]], @@ -584,19 +573,23 @@ def __is_class( colors["code"].append(value.text) return colors - def __get_multi_obj(self) -> Dict[str, Union[str, list]]: + def __get_multi_object(self) -> Dict[str, Union[str, list]]: # get an object consisting of available color codes to be # randomized and available subclasses to match against # randomized codes colors = {"code": [], "classes": []} for key, value in self.__dict__.items(): - colors = self.__is_class(value, key, colors) + colors = self.__collect_values(value, key, colors) colors["code"] = list(dict.fromkeys(colors["code"])) return colors @staticmethod - def __valid_code( - class_: Color, code: int, str_: str, count: int, full_str: List[str] + def __validate_code( + class_: Color, + code: int, + str_: str, + count: int, + full_str: List[str] ) -> List[str]: # match the ansi escape code against randomized number to be # used to color string index @@ -611,43 +604,95 @@ def __get_multi_str( # compile and return the string colored by a randomized # assortment of colors present with the available subclasses full_str = [] - code = randint(colors["code"][0], colors["code"][-1]) + if not colors["code"]: + return str_ for count, _ in enumerate(str_): + code = randint(colors["code"][0], colors["code"][-1]) for class_ in colors["classes"]: - full_str = self.__valid_code( + full_str = self.__validate_code( class_, code, str_, count, full_str ) return "".join(full_str) + @staticmethod + def get_list(str_: str) -> List[str]: + """Split string up by ansi escape codes and return list of all + sections of the string + + :param str_: String containing ansi escape codes + :return: List of ordered escape codes and substrings + """ + words = re.split(Color.ansi_escape, str_) + return [word for word in words if word != ""] + + def set_str(self, str_: str, name_: str = "helper") -> str: + """Separate ansi code from string, creating a Color subclass + from the pre-existing color, available in self, and return the + bare string + + :param str_: String containing ansi escape codes to + instantiate class from its codes + :param name_: Name of subclass + :return: String stripped of ansi codes + """ + state = self.get_object(str_, name_) + if state["str_"]: + str_ = state.pop("str_") + self.__make_subclass((), state) + return str_ + + def get_object( + self, str_: str, name_: str = "helper" + ) -> Dict[str, Union[str, Dict[str, str]]]: + """Split ansi codes and string and populate the object + representing the string and its color before they were + separated to restore its state later + + .. todo:: + - Make this work for multicolor which consists of more than + just one code + - Currently the only code captured will be the first one + - This will also include changes to self.get_key() which + will need to iterate through several items in object and + put them back in the right place + + + :param str_: String containing ansi escape codes + :param name_: Name of the subclass to be created + :return: Dictionary containing separated escape codes and + str + """ + words = self.get_list(str_) + return self.__get_state_object(words, name_) + def set(self, *args: Any, **kwargs: Any) -> None: """Call to set new instance values + colors: + - black: 0 + - red: 1 + - green: 2 + - yellow: 3 + - blue: 4 + - purple: 5 + - cyan: 6 + - white: 7 + + effects: + - None: 0 + - bold: 1 + - bright: 2 + - underline: 3 + - negative: 4 + :param args: Colors or effects as integers or strings Without keywords args are positional like so: >>> Color("text", "effect", "background") - colors: - - black: 0 - - red: 1 - - green: 2 - - yellow: 3 - - blue: 4 - - purple: 5 - - cyan: 6 - - white: 7 - - effects: - - None: 0 - - bold: 1 - - bright: 2 - - underline: 3 - - negative: 4 - - e.g. - >>> color = Color() - >>> color.set(2, 1, 1) + >>> color = Color() + >>> color.set(2, 1, 1) - text: green - effect: bold @@ -655,26 +700,25 @@ def set(self, *args: Any, **kwargs: Any) -> None: :param kwargs: More precise keyword arguments - e.g. - >>> color = Color() - - >>> # instance attributes - >>> color.set( - ... text="green", - ... effect="bold", - ... background="red" - ... ) - - >>> # subclasses - set like those for - >>> # original class only keyword arguments - >>> # are expressed as dictionary - >>> color.set( - ... sub_color={ - ... "text": "green", - ... "effect": "bold", - ... "background": "red" - ... } - ... ) + >>> color = Color() + + >>> # instance attributes + >>> color.set( + ... text="green", + ... effect="bold", + ... background="red" + ... ) + + >>> # subclasses - set like those for + >>> # original class only keyword arguments + >>> # are expressed as dictionary + >>> color.set( + ... sub_color={ + ... "text": "green", + ... "effect": "bold", + ... "background": "red" + ... } + ... ) """ kwargs = self.__populate_defaults(kwargs) @@ -684,11 +728,11 @@ def set(self, *args: Any, **kwargs: Any) -> None: def pop(self, str_: str) -> Optional[Any]: """Retrieve attr present with class instance - e.g. - >>> color = Color(subclass={"text": "red"}) - >>> red = color.pop("subclass") - >>> print(red.__dict__) - ... {'text': 1, 'effect': 0, 'background': 0} + + >>> color = Color(subclass={"text": "red"}) + >>> red = color.pop("subclass") + >>> print(red.__dict__) + ... {'text': 1, 'effect': 0, 'background': 0} :param str_: Key to remove :return: Class dict or None @@ -699,7 +743,7 @@ def pop(self, str_: str) -> Optional[Any]: return None def get( - self, *args: Union[str, int], reset: Optional[str] = __reset + self, *args: Union[str, int], reset: Optional[str] = reset ) -> Union[str, Tuple[Union[str, Any], ...]]: """Return colored string @@ -716,8 +760,8 @@ def get( :return: Colored string """ if len(args) > 1: - return self.__color_tuple(args, reset) - return self.__color_str(args[0], reset) + return self.__get_colored_tuple(args, reset) + return self.__get_colored_str(args[0], reset) def get_key( self, @@ -754,9 +798,9 @@ def get_key( :return: String with selected key(s) colored """ switches = {"any": scatter, "case": ignore_case} - if Color.__code in str_: + if Color.code in str_: return self.__resolve_ansi_code(search, str_, switches) - return self.__manipulate_string(search, str_, Color.__reset, switches) + return self.__process_str(search, str_, Color.reset, switches) def multicolor(self, str_: str) -> str: """Return string colored with an assortment of all colors @@ -782,7 +826,7 @@ def multicolor(self, str_: str) -> str: :param str_: String to color :return: Colored string """ - colors = self.__get_multi_obj() + colors = self.__get_multi_object() return self.__get_multi_str(str_, colors) def populate_colors(self) -> None: @@ -794,7 +838,7 @@ def populate_colors(self) -> None: def print( self, *args: Union[str, int], multi=False, **kwargs: Dict[str, str], ) -> None: - """print colored strings straight to stdout + """Print colored strings straight to stdout builtin print() kwargs valid keyword arguments >>> color = Color("red", "bold", "green") @@ -819,7 +863,7 @@ def print_key( ignore_case: bool = False, **kwargs: bool, ) -> None: - """color search key-words + """Search for and then color key-words >>> color = Color(1) >>> color.print_key("str to color", "str") diff --git a/requirements.txt b/requirements.txt index c319dffb..ea185e1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,47 +4,59 @@ astroid==2.3.3 attrs==19.3.0 Babel==2.8.0 backcall==0.1.0 +bleach==3.1.0 certifi==2019.11.28 +cffi==1.13.2 cfgv==2.0.1 chardet==3.0.4 coverage==5.0.3 +cryptography==2.8 decorator==4.4.1 docutils==0.16 entrypoints==0.3 flake8==3.7.9 +git-changelog==0.2.0 identify==1.4.11 idna==2.8 imagesize==1.2.0 -ipython==7.11.1 +ipython==7.12.0 ipython-genutils==0.2.0 isort==4.3.21 jedi==0.16.0 -Jinja2==2.11.0 +jeepney==0.4.2 +Jinja2==2.11.1 +keyring==21.1.0 lazy-object-proxy==1.4.3 MarkupSafe==1.1.1 mccabe==0.6.1 -more-itertools==8.1.0 +more-itertools==8.2.0 nodeenv==1.3.4 packaging==20.1 parso==0.6.0 pexpect==4.8.0 pickleshare==0.7.5 +pkginfo==1.5.0.1 pluggy==0.13.1 -pre-commit==1.21.0 +pre-commit==2.0.1 prompt-toolkit==3.0.3 ptyprocess==0.6.0 py==1.8.1 pycodestyle==2.5.0 +pycparser==2.19 pyflakes==2.1.1 Pygments==2.5.2 pylint==2.4.4 pyparsing==2.4.6 -pytest==5.3.4 +pytest==5.3.5 pytest-cov==2.8.1 pytest-sugar==0.9.2 pytz==2019.3 PyYAML==5.3 +readme-renderer==24.0 requests==2.22.0 +requests-toolbelt==0.9.1 +restview==2.9.2 +SecretStorage==3.1.2 six==1.14.0 snowballstemmer==2.0.0 Sphinx==2.3.1 @@ -52,13 +64,17 @@ sphinxcontrib-applehelp==1.0.1 sphinxcontrib-devhelp==1.0.1 sphinxcontrib-htmlhelp==1.0.2 sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-programoutput==0.15 sphinxcontrib-qthelp==1.0.2 sphinxcontrib-serializinghtml==1.1.3 termcolor==1.1.0 toml==0.10.0 +tqdm==4.42.0 traitlets==4.3.3 +twine==3.1.1 urllib3==1.25.8 virtualenv==16.7.9 vulture==1.2 wcwidth==0.1.8 +webencodings==0.5.1 wrapt==1.11.2 diff --git a/tests/_test.py b/tests/_test.py index f81c262f..fd7424d6 100644 --- a/tests/_test.py +++ b/tests/_test.py @@ -1,81 +1,46 @@ #!/usr/bin/env python3 -from object_colors import Color - - -def test_color_string(str_, color_str) -> None: - assert color_str == f"\u001b[0;32;40m{str_}\u001b[0;0m" - - -def test__getattr__(): - color = Color() - hasattr(color, "__self__") and color.__self__ is color.get - - -def test__dir__(color, capsys): - instances = color.__dir__() - assert instances == [ - "text", - "effect", - "background", - "bold", - "black", - "red", - "green", - "yellow", - "blue", - "purple", - "cyan", - "white", - ] - - -def test_pop_result(color): - assert hasattr(color, "red") - red = color.pop("red") - assert "red" not in color.__dict__ - red_string = red.get("This is red") - assert red_string == f"\u001b[0;31;40mThis is red\u001b[0;0m" - +from typing import List -def test_pop_no_result(): - color = Color() - assert "red" not in color.__dict__ - red = color.pop("red") - assert red is None +from pytest import fixture - -def test_str_args(): - color = Color("red", "bold") - assert color.text == 1 - assert color.effect == 1 - - -def test_str_ints(): - color = Color(1, 1) - assert color.text == 1 - assert color.effect == 1 - - -def test_tuple_return(color): - tup = color.red.get("t", "u", "p") - assert tup == ( - "\u001b[0;31;40mt\u001b[0;0m", - "\u001b[0;31;40mu\u001b[0;0m", - "\u001b[0;31;40mp\u001b[0;0m", - ) - - -def test_color_print(color, capsys): - color.red.print("This stdout is red") - captured = capsys.readouterr() - assert captured.out == "\u001b[0;31;40mThis stdout is red\u001b[0;0m\n" - - -def test_int_dict(): - color = Color(orange=1) - assert color.text == 7 +from object_colors import Color -def test_rainbow(color, str_, capsys): - rb = color.multicolor(str_) - print(rb) +class Test: + + def test_color_string(self, str_, green, color_str, reset) -> None: + assert color_str == f"{green}{str_}{reset}" + + def test__getattr__(self, attrs: List[str]) -> None: + color = Color() + for attr in attrs: + assert hasattr(color, attr) + + def test__dir__( + self, color: Color, instances: List[str], capsys: fixture + ) -> None: + color_instances = color.__dir__() + assert color_instances == instances + + def test_str_args(self) -> None: + color = Color("red", "bold", "green") + assert color.text == 1 + assert color.effect == 1 + assert color.background == 2 + + def test_str_ints(self) -> None: + color = Color(1, 1, 2) + assert color.text == 1 + assert color.effect == 1 + assert color.background == 2 + + def test_color_print( + self, color: Color, red: str, capsys: fixture, reset: str + ) -> None: + color.red.print("This stdout is red") + captured = capsys.readouterr() + assert captured.out == f"{red}This stdout is red{reset}\n" + + def test_int_dict(self) -> None: + color = Color(orange=1) + assert color.text == 7 diff --git a/tests/get_key_test.py b/tests/get_key_test.py index 5bb1841f..2bb5f9eb 100644 --- a/tests/get_key_test.py +++ b/tests/get_key_test.py @@ -43,9 +43,9 @@ def test_dupe_words( self, color: Color, colored_dupes: str, - dupe_marked_color, - dupes, - dupe_marked, + dupe_marked_color: str, + dupes: str, + dupe_marked: str, ) -> None: keys = color.red.get_key(dupes, "one") assert keys == dupe_marked @@ -138,8 +138,8 @@ def test_exact_letter_in_string( color: Color, color_str: str, str_: str, - scatter_cs_exact_color, - scatter_cs_exact, + scatter_cs_exact_color: str, + scatter_cs_exact: str, ) -> None: keys = color.red.get_key(str_, "c", scatter=True) assert keys == scatter_cs_exact diff --git a/tests/get_test.py b/tests/get_test.py new file mode 100644 index 00000000..b7ea195b --- /dev/null +++ b/tests/get_test.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + + +class TestGet: + + def test_tuple_return(self, color): + tup = color.red.get("t", "u", "p") + assert tup == ( + "\u001b[0;31;40mt\u001b[0;0m", + "\u001b[0;31;40mu\u001b[0;0m", + "\u001b[0;31;40mp\u001b[0;0m", + ) diff --git a/tests/multicolor_test.py b/tests/multicolor_test.py index af67c481..1c3208a8 100644 --- a/tests/multicolor_test.py +++ b/tests/multicolor_test.py @@ -1,6 +1,33 @@ #!/usr/bin/env python3 +from typing import List + from object_colors import Color class TestMultiColor: - _ = Color() + + def test_multi_values( + self, color: Color, colors: List[str], for_multiple_colors: str + ) -> None: + rb = color.multicolor(for_multiple_colors) + ansis = color.get_list(rb) + for ansi in ansis: + if Color.ansi_escape.match(ansi): + for str_ in ansi: + if str_.isdigit(): + assert int(str_) <= 7 + + def test_multi_empty(self, for_multiple_colors): + color = Color() + rb = color.multicolor(for_multiple_colors) + assert rb == for_multiple_colors + + def test_multi_increment(self, colors): + args = [] + color_class = Color() + for count, color in enumerate(colors): + color_class.set({color: {"text": color}}) + args.append(color) + tups = tuple(args) + for tup in tups: + assert hasattr(color_class, tup) diff --git a/tests/pop_test.py b/tests/pop_test.py new file mode 100644 index 00000000..e6d8a6d5 --- /dev/null +++ b/tests/pop_test.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +from object_colors import Color + + +class TestPop: + + def test_pop_result(self, color): + assert hasattr(color, "red") + red = color.pop("red") + assert "red" not in color.__dict__ + red_string = red.get("This is red") + assert red_string == f"\u001b[0;31;40mThis is red\u001b[0;0m" + + def test_pop_no_result(self): + color = Color() + assert "red" not in color.__dict__ + red = color.pop("red") + assert red is None