From c7bf3f819ab1ecb312aaf5b6cbe177b32d46d6ad Mon Sep 17 00:00:00 2001 From: Olga Ivanova Date: Thu, 12 Sep 2024 11:19:57 +0200 Subject: [PATCH] extended vis aux for nodes names adjustments, added tests for aux --- networkcommons/visual/_aux.py | 87 ++++++++++++++++++++++--- tests/test_vis_aux.py | 115 ++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 tests/test_vis_aux.py diff --git a/networkcommons/visual/_aux.py b/networkcommons/visual/_aux.py index 1a05d89..a3819ff 100644 --- a/networkcommons/visual/_aux.py +++ b/networkcommons/visual/_aux.py @@ -13,12 +13,83 @@ # https://www.gnu.org/licenses/gpl-3.0.txt # +from typing import List +from networkcommons._session import _log +import re -def wrap_node_name(node_name): - if ":" in node_name: - node_name = node_name.replace(":", "_") - if node_name.startswith("COMPLEX"): - # remove the word COMPLEX with a separator (:/-, etc) - return node_name[8:] - else: - return node_name + +def adjust_node_name(node_name: str, + truncate: bool = False, + wrap: bool = False, + max_length: int = 8, + wrap_length: int = 8, + ensure_unique: bool = False, + ensure_unique_list: List[str] = None, + remove_strings: List[str] = None) -> str: + """ + Adjust the node name by replacing special characters, truncating, wrapping, and ensuring uniqueness. + + Parameters + ---------- + node_name : str + The node name to adjust. + truncate : bool, optional + Whether to truncate the node name, by default False. + wrap : bool, optional + Whether to wrap the node name, by default False. + max_length : int, optional + The maximum length of the node name, by default 8. + wrap_length : int, optional + The length at which to wrap the node name, by default 8. + ensure_unique : bool, optional + Whether to ensure the node name is unique, by default False. + ensure_unique_list : List[str], optional + A list of node names to ensure uniqueness against, by default None. + remove_strings : List[str], optional + A list of substrings to remove from the node name, by default None. + """ + + # Replace any ':' with '_' + node_name = node_name.replace(":", "_") + + # Remove provided substrings, if any + if remove_strings: + for string in remove_strings: + node_name = node_name.replace(string, "") + + # Replace special symbols (anything non-alphanumeric or underscore) with '_' + new_node_name = re.sub(r'[^a-zA-Z0-9_]', '_', node_name) + + # Log any replacement of special symbols + if new_node_name != node_name: + _log(f"Replaced special characters in '{node_name}' with underscores.", level=10) + + # If the modified node name is longer than the specified maximum length, truncate it + if truncate and len(new_node_name) > max_length: + new_node_name = new_node_name[:max_length] + ".." + _log(f"Truncated node name '{node_name}' to '{new_node_name}'.", level=10) + + # If the wrap flag is set to True, wrap the node name + if wrap: + new_node_name = "\n".join([new_node_name[i:i + wrap_length] + for i in range(0, len(new_node_name), wrap_length)]) + + # If multiple underscores are present, replace them with a single underscore + new_node_name = re.sub(r'_+', '_', new_node_name) + + # If the node name is empty, return an empty string and log a warning + if not new_node_name: + _log("Empty node name provided. Returning an empty string.", level=20) + return "" + + if ensure_unique and ensure_unique_list: + # Ensure the modified node name is unique by appending a number + if new_node_name in ensure_unique_list: + i = 1 + while f"{new_node_name}_{i}" in ensure_unique_list: + i += 1 + new_node_name = f"{new_node_name}_{i}" + _log(f"Ensured unique node name by appending a number: '{new_node_name}'.", level=10) + + # Return the modified node name, stripped of leading and trailing spaces, and _ characters + return new_node_name.strip("_") diff --git a/tests/test_vis_aux.py b/tests/test_vis_aux.py new file mode 100644 index 0000000..226779f --- /dev/null +++ b/tests/test_vis_aux.py @@ -0,0 +1,115 @@ +import pytest +from networkcommons.visual._aux import adjust_node_name + + +def test_replace_colon(): + assert adjust_node_name( + "node:name" + ) == "node_name" + + +def test_remove_provided_strings(): + # Remove 'COMPLEX' and 'ABC' + assert adjust_node_name( + "COMPLEX:ABC:node", + remove_strings=["COMPLEX", "ABC"] + ) == "node" + + +def test_special_character_replacement(): + # Replace special characters with '_' + assert adjust_node_name( + "node@name#1!" + ) == "node_name_1" + + +def test_truncate_node_name(): + # Truncate node name to max_length + assert adjust_node_name( + "VeryLongNodeName", + truncate=True, + max_length=8 + ) == "VeryLong.." + + +def test_wrap_node_name(): + # Wrap node name into lines of wrap_length + assert adjust_node_name( + "VeryLongNodeName", + wrap=True, + wrap_length=4 + ) == "Very\nLong\nNode\nName" + + +def test_no_truncate_or_wrap(): + # Test without truncation or wrapping + assert adjust_node_name( + "node_name", + truncate=False, + wrap=False + ) == "node_name" + + +def test_empty_node_name(): + # Handle empty node name + assert adjust_node_name( + "", + remove_strings=["COMPLEX"] + ) == "" + + +def test_strip_whitespace(): + # Ensure leading and trailing spaces are stripped + assert adjust_node_name( + " COMPLEX:node ", + remove_strings=["COMPLEX"] + ) == "node" + + +def test_multiple_underscores(): + # Test reducing multiple underscores to a single one + assert adjust_node_name( + "node___name" + ) == "node_name" + + +def test_unique_node_name(): + # Test ensuring uniqueness by appending a number + assert adjust_node_name( + "node_name", + ensure_unique=True, + ensure_unique_list=["node_name", "node_name_1"] + ) == "node_name_2" + + +def test_ensure_unique_with_empty_list(): + # Test ensuring uniqueness when the list is empty + assert adjust_node_name( + "node_name", + ensure_unique=True, + ensure_unique_list=[] + ) == "node_name" + + +def test_empty_name_after_modifications(): + # Test if name becomes empty after all modifications + assert adjust_node_name( + "COMPLEX:ABC", + remove_strings=["COMPLEX", "ABC"] + ) == "" + + +def test_trailing_underscores(): + # Test if trailing underscores are removed after modifications + assert adjust_node_name( + "node___", + remove_strings=["___"] + ) == "node" + + +def test_leading_trailing_underscores_trimmed(): + # Test trimming of leading and trailing underscores + assert adjust_node_name( + "___node___", + remove_strings=[] + ) == "node"