Skip to content

Commit 9d39c14

Browse files
committed
Add a common_prefix helper function in string_utils module
This is a replacement for the os.path.commonprefix function which is deprecated in Python 3.15. Python 3.15 recommends using os.path.commonpath, but that isn't equivalent and yields different results.
1 parent 3bb5d42 commit 9d39c14

4 files changed

Lines changed: 52 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ prompt is displayed.
8181
- `cmd2.Cmd.select` has been revamped to use the
8282
[choice](https://python-prompt-toolkit.readthedocs.io/en/3.0.52/pages/asking_for_a_choice.html)
8383
function from `prompt-toolkit` when both **stdin** and **stdout** are TTYs
84+
- Added `common_prefix` method to `cmd2.string_utils` module as a replacement for
85+
`os.path.commonprefix` since that is now deprecated in Python 3.15
8486

8587
## 3.4.0 (March 3, 2026)
8688

cmd2/cmd2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1921,7 +1921,7 @@ def delimiter_complete(
19211921
match_strings = basic_completions.to_strings()
19221922

19231923
# Calculate what portion of the match we are completing
1924-
common_prefix = os.path.commonprefix(match_strings)
1924+
common_prefix = su.common_prefix(match_strings)
19251925
prefix_tokens = common_prefix.split(delimiter)
19261926
display_token_index = len(prefix_tokens) - 1
19271927

cmd2/string_utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
full-width characters (like those used in CJK languages).
66
"""
77

8+
from collections.abc import (
9+
Sequence,
10+
)
11+
812
from rich.align import AlignMethod
913
from rich.style import StyleType
1014
from rich.text import Text
@@ -167,3 +171,22 @@ def norm_fold(val: str) -> str:
167171
import unicodedata
168172

169173
return unicodedata.normalize("NFC", val).casefold()
174+
175+
176+
def common_prefix(m: Sequence[str]) -> str:
177+
"""Return the longest common leading component of a list of strings.
178+
179+
This is a replacement for os.path.commonprefix which is deprecated in Python 3.15.
180+
181+
:param m: list of strings
182+
:return: common prefix
183+
"""
184+
if not m:
185+
return ""
186+
187+
s1 = min(m)
188+
s2 = max(m)
189+
for i, c in enumerate(s1):
190+
if i >= len(s2) or c != s2[i]:
191+
return s1[:i]
192+
return s1

tests/test_string_utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,29 @@ def test_unicode_casefold() -> None:
246246
micro_cf = micro.casefold()
247247
assert micro != micro_cf
248248
assert su.norm_fold(micro) == su.norm_fold(micro_cf)
249+
250+
251+
def test_common_prefix() -> None:
252+
# Empty list
253+
assert su.common_prefix([]) == ""
254+
255+
# Single item
256+
assert su.common_prefix(["abc"]) == "abc"
257+
258+
# Common prefix exists
259+
assert su.common_prefix(["abcdef", "abcde", "abcd"]) == "abcd"
260+
261+
# No common prefix
262+
assert su.common_prefix(["abc", "def"]) == ""
263+
264+
# One is a prefix of another
265+
assert su.common_prefix(["apple", "app"]) == "app"
266+
267+
# Identical strings
268+
assert su.common_prefix(["test", "test"]) == "test"
269+
270+
# Case sensitivity (matches os.path.commonprefix behavior)
271+
assert su.common_prefix(["Apple", "apple"]) == ""
272+
273+
# Empty string in list
274+
assert su.common_prefix(["abc", ""]) == ""

0 commit comments

Comments
 (0)