diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index d522c46..b65a333 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -175,3 +175,44 @@ def pad_number(index, pad_length=2): '0042' """ return str(index).zfill(pad_length) + + +def text_in_last_bracket_group(text): + """Return the content of the last balanced square-bracket group. + + Parameters + ---------- + text : str + The input string to search for the last balanced square-bracket group. + + Returns + ------- + str or None + The content of the last balanced square-bracket group, or None if no + such group exists. + + Examples + -------- + >>> text_in_last_bracket_group("foo[bar]baz") + 'bar' + >>> text_in_last_bracket_group("foo[bar[baz]qux]") + 'bar[baz]qux' + >>> text_in_last_bracket_group("foo[]bar") + '' + >>> text_in_last_bracket_group("foobar") + None + """ + depth = 0 + end = None + + for index in range(len(text) - 1, -1, -1): + if text[index] == "]": + if end is None: + end = index + depth += 1 + elif text[index] == "[": + depth -= 1 + if depth == 0: + return text[index + 1 : end] + + return None diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 87cf17c..be73293 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -1,13 +1,17 @@ """Tests for `imcflibs.strtools`.""" # -*- coding: utf-8 -*- -import pytest import os -from imcflibs.strtools import _is_string_like -from imcflibs.strtools import filename -from imcflibs.strtools import flatten -from imcflibs.strtools import strip_prefix +import pytest + +from imcflibs.strtools import ( + _is_string_like, + filename, + flatten, + strip_prefix, + text_in_last_bracket_group, +) def test__is_string_like(): @@ -37,3 +41,51 @@ def test_strip_prefix(): """Test `strip_prefix()`.""" assert strip_prefix("foobar", "foo") == "bar" assert strip_prefix("foobar", "bar") == "foobar" + + +def test_text_in_last_bracket_group_basic(): + """Test `text_in_last_bracket_group()` with a single bracket group.""" + assert text_in_last_bracket_group("foo[bar]baz") == "bar" + + +def test_text_in_last_bracket_group_multiple(): + """Test `text_in_last_bracket_group()` with multiple bracket groups.""" + assert text_in_last_bracket_group("foo[bar]baz[qux]") == "qux" + + +def test_text_in_last_bracket_group_nested(): + """Test `text_in_last_bracket_group()` with nested bracket groups.""" + assert text_in_last_bracket_group("foo[bar[baz]qux]") == "bar[baz]qux" + + +def test_text_in_last_bracket_group_empty(): + """Test `text_in_last_bracket_group()` with an empty bracket group.""" + assert text_in_last_bracket_group("foo[]bar") == "" + + +def test_text_in_last_bracket_group_no_brackets(): + """Test `text_in_last_bracket_group()` with no bracket groups.""" + assert text_in_last_bracket_group("foobar") is None + + +def test_text_in_last_bracket_group_empty_string(): + """Test `text_in_last_bracket_group()` with an empty string.""" + assert text_in_last_bracket_group("") is None + + +def test_text_in_last_bracket_group_only_brackets(): + """Test `text_in_last_bracket_group()` with only brackets.""" + assert text_in_last_bracket_group("[foo]") == "foo" + + +def test_text_in_last_bracket_group_deeply_nested(): + """Test `text_in_last_bracket_group()` with deeply nested brackets.""" + assert text_in_last_bracket_group("foo[bar[baz[qux]]]") == "bar[baz[qux]]" + + +def test_multi_series_files(): + """Test `text_in_last_bracket_group()` with multiple series files.""" + filename = "ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2" + input = "%s [%s (series 1)]" % (filename, filename) + expected = "%s (series 1)" % filename + assert text_in_last_bracket_group(input) == expected