From 595b07930a90d3e678a145992d03876182d0ebab Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 14 Sep 2026 15:39:16 +0200 Subject: [PATCH 1/6] =?UTF-8?q?feat(strtools):=20=E2=9C=A8=20add=20method?= =?UTF-8?q?=20for=20extracting=20text=20between=20brackets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is particularly important for multi series file in OMERO as they are named: file_name.ext [file_name.ext (seriesx)] --- src/imcflibs/strtools.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index d522c460..c7f81417 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -175,3 +175,33 @@ 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 + """ + 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 From cefb50e8b87ee2a5bf16bf00a85e3839e52e4efe Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 6 Oct 2026 17:04:51 +0200 Subject: [PATCH 2/6] =?UTF-8?q?test(strtools):=20=E2=9C=85=20add=20tests?= =?UTF-8?q?=20for=20new=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_strtools.py | 64 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 87cf17c5..fa7399f0 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,53 @@ 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.""" + assert ( + text_in_last_bracket_group( + "ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2 [ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2 (series 1)]" + ) + == "ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2 (series 1)" + ) From b7891f078e0fff4388c5aa63098062dbd9d96f40 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 8 Oct 2026 16:20:49 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=A7=BD=20Improve=20test=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_strtools.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_strtools.py b/tests/test_strtools.py index fa7399f0..918c811a 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -85,9 +85,8 @@ def test_text_in_last_bracket_group_deeply_nested(): 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" assert ( - text_in_last_bracket_group( - "ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2 [ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2 (series 1)]" - ) - == "ChannelSD - DAPI 20x,SD - GFP 20x,SD - Cy3 20x,SD - Cy5 20x_Seq0002.nd2 (series 1)" + text_in_last_bracket_group(f"{filename} [{filename} (series 1)]") + == f"{filename} (series 1)" ) From c980bf7649ff098cbec48905a7f21c2903c32116 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 8 Oct 2026 16:35:29 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=A9=B9=20Restore=20Python=202=20compa?= =?UTF-8?q?tibility=20=F0=9F=90=8D=F0=9F=AA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_strtools.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 918c811a..be73293a 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -86,7 +86,6 @@ def test_text_in_last_bracket_group_deeply_nested(): 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" - assert ( - text_in_last_bracket_group(f"{filename} [{filename} (series 1)]") - == f"{filename} (series 1)" - ) + input = "%s [%s (series 1)]" % (filename, filename) + expected = "%s (series 1)" % filename + assert text_in_last_bracket_group(input) == expected From ae1ee0855b35243bc6cbb70da649cca361915f4e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 8 Oct 2026 16:51:30 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=A7=BD=20Fix=20line=20length=20and=20?= =?UTF-8?q?full=20stop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/imcflibs/strtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index c7f81417..f46978b5 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -188,8 +188,8 @@ def text_in_last_bracket_group(text): Returns ------- str or None - The content of the last balanced square-bracket group, or None if no such - group exists + The content of the last balanced square-bracket group, or None if no + such group exists. """ depth = 0 end = None From 402848894edf668a2268f0d20a4b383d8c1db67c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 8 Oct 2026 16:51:48 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=93=9D=20Add=20usage=20examples=20to?= =?UTF-8?q?=20docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/imcflibs/strtools.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index f46978b5..b65a333f 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -190,6 +190,17 @@ def text_in_last_bracket_group(text): 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