The functional-nesting map/lambda style is difficult to understand and maintain. I discourage this sort of code. It also loses the ability to generate meaningful messages describing the specific path that produces an error.
I propose that you use regular expressions, among other reasons because a regex will be able to capture and verify a consistent stem, suffix and suffix index - you're not doing this currently. pathlib is helpful but not helpful enough.
Write unit tests.
Your code would benefit from detecting and reporting the RAR naming variant used.
Suggested
import enum
import re
import typing
from pathlib import Path
class RarVersion(enum.IntEnum):
AMBIGUOUS = 0
V3 = 3
V5 = 5
V3_PAT = re.compile(
r'''(?x)
^ # start
(?P<stem>
.+ # require a stem of at least one character
)
\. # suffix separator dot
(?P<suffix>
rar | # first literal 'rar' suffix
r(?P<index>
\d\d # two-digit index
)
)
$ # end
''')
V5_PAT = re.compile(
r'''(?x)
^ # start
(?P<stem>
.+ # require a stem of at least one character
)
\.
(?P<suffix>
part # beginning of first suffix component
(?P<index>
\d+ # at least one digit
)
\.rar # last suffix component
)
$ # end
''')
def rar_list_verify(paths: typing.Sequence[str | Path]) -> RarVersion:
matches = [V5_PAT.match(str(p)) for p in paths]
if any(m is None for m in matches):
matches = [V3_PAT.match(str(p)) for p in paths]
version = RarVersion.V3
for path, match in zip(paths, matches):
if match is None:
raise ValueError(f'{path} does not match the version-3 pattern')
elif len(paths) > 1:
version = RarVersion.V5
else:
version = RarVersion.AMBIGUOUS
stem = matches[0]['stem']
for i, match in enumerate(matches[1:], start=1):
if match['stem'] != stem:
raise ValueError(f'{paths[i]} has an inconsistent stem')
actual = {
int(match['index'])
for match in matches
if match['index']
}
match version:
case RarVersion.V3:
base = 0
count = len(paths) - 1
case RarVersion.V5:
base = 1
count = len(paths)
case RarVersion.AMBIGUOUS:
# It's only possible for this to be a valid V5 if the only index is 1
if actual == {1}:
return version
version = RarVersion.V3
if version == RarVersion.V3:
n_unnumbered = sum(
1 for match in matches
if match['suffix'] == 'rar'
)
if n_unnumbered != 1:
raise ValueError(f'{n_unnumbered} paths have a non-indexed suffix; must be exactly one')
expected = set(range(base, base + count))
spurious = actual - expected
if spurious:
raise ValueError(
'The following indices are unexpected: '
+ ', '.join(str(i) for i in spurious)
)
missing = expected - actual
if missing:
raise ValueError(
'The following indices are missing: '
+ ', '.join(str(i) for i in missing)
)
return version
def test() -> None:
assert rar_list_verify(
('a.part1.rar', 'a.part2.rar')
) == RarVersion.V5, 'Simple V5'
assert rar_list_verify(
('a.rar', 'a.r00', 'a.r01')
) == RarVersion.V3, 'Simple V3'
assert rar_list_verify(
('a.rar',)
) == RarVersion.V3, 'Almost ambiguous but cannot be V5'
assert rar_list_verify(
('a.part1.rar',)
) == RarVersion.AMBIGUOUS, 'Actually ambiguous even though it is likely V5'
try:
rar_list_verify(('a.part2.rar',))
raise AssertionError('Invalid index forces this to be interpreted as V3')
except ValueError as e:
assert 'paths have a non-indexed suffix' in str(e)
if __name__ == '__main__':
test()