Skip to content

Commit 3be2a51

Browse files
committed
Strip whitespace from frame range in FrameSet constructor (fixes #137)
1 parent 6443706 commit 3be2a51

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

src/fileseq/frameset.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def catch_parse_err(fn, *a, **kw): # type: ignore
177177
frange = str(frange)
178178
for key in self.PAD_MAP:
179179
frange = frange.replace(key, '')
180+
frange = ''.join(frange.split()) # https://github.com/justinfx/fileseq/issues/137: strip all whitespace
180181
self._frange = asString(frange)
181182

182183
# because we're acting like a set, we need to support the empty set

test/test_unit.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,175 @@ def testBatches(self):
657657
actual = list(FrameSet(case.input).batches(case.batch, frames=False))
658658
self.assertListEqual(expect, actual, msg=str(case))
659659

660+
def testWhitespaceHandling(self):
661+
"""
662+
Issue #137: Whitespace tolerance in frame range parsing.
663+
664+
Test that FrameSet correctly handles whitespace in various positions:
665+
- Spaces after commas
666+
- Spaces around range operators
667+
- Leading/trailing spaces
668+
- Spaces with modifiers (x, y, :)
669+
- Tabs and newlines
670+
"""
671+
@dataclasses.dataclass
672+
class Case:
673+
input: str
674+
expected_frange: str
675+
expected_frames: list[int]
676+
description: str
677+
678+
table = [
679+
# Basic whitespace after commas
680+
Case(
681+
input='1, 2, 3',
682+
expected_frange='1,2,3',
683+
expected_frames=[1, 2, 3],
684+
description='spaces after commas'
685+
),
686+
Case(
687+
input='1 , 2 , 3',
688+
expected_frange='1,2,3',
689+
expected_frames=[1, 2, 3],
690+
description='spaces before and after commas'
691+
),
692+
693+
# Whitespace around range operators
694+
Case(
695+
input='1 - 10',
696+
expected_frange='1-10',
697+
expected_frames=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
698+
description='spaces around dash'
699+
),
700+
Case(
701+
input='1 - 10',
702+
expected_frange='1-10',
703+
expected_frames=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
704+
description='multiple spaces around dash'
705+
),
706+
707+
# Leading and trailing whitespace
708+
Case(
709+
input=' 1-5 ',
710+
expected_frange='1-5',
711+
expected_frames=[1, 2, 3, 4, 5],
712+
description='leading and trailing spaces'
713+
),
714+
Case(
715+
input=' 1 ',
716+
expected_frange='1',
717+
expected_frames=[1],
718+
description='spaces around single frame'
719+
),
720+
721+
# Whitespace with x modifier (step)
722+
Case(
723+
input='1-10 x 2',
724+
expected_frange='1-10x2',
725+
expected_frames=[1, 3, 5, 7, 9],
726+
description='spaces with x modifier'
727+
),
728+
Case(
729+
input='1 - 10 x 2',
730+
expected_frange='1-10x2',
731+
expected_frames=[1, 3, 5, 7, 9],
732+
description='spaces in range and x modifier'
733+
),
734+
735+
# Whitespace with y modifier (fill)
736+
Case(
737+
input='1-10 y 2',
738+
expected_frange='1-10y2',
739+
expected_frames=[2, 4, 6, 8, 10],
740+
description='spaces with y modifier'
741+
),
742+
743+
# Whitespace with : modifier (stagger)
744+
Case(
745+
input='1-6 : 3',
746+
expected_frange='1-6:3',
747+
expected_frames=[1, 4, 3, 5, 2, 6],
748+
description='spaces with : modifier'
749+
),
750+
751+
# Complex case with multiple comma-separated ranges
752+
Case(
753+
input='1 , 2 , 3 , 5-10 , 20-30',
754+
expected_frange='1,2,3,5-10,20-30',
755+
expected_frames=[1, 2, 3, 5, 6, 7, 8, 9, 10, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
756+
description='complex multi-range with spaces'
757+
),
758+
Case(
759+
input=' 1 , 5-10 , 20 ',
760+
expected_frange='1,5-10,20',
761+
expected_frames=[1, 5, 6, 7, 8, 9, 10, 20],
762+
description='multiple spaces in complex range'
763+
),
764+
765+
# Tabs (should also be removed)
766+
Case(
767+
input='1\t,\t2\t,\t3',
768+
expected_frange='1,2,3',
769+
expected_frames=[1, 2, 3],
770+
description='tabs instead of spaces'
771+
),
772+
Case(
773+
input='1\t-\t10',
774+
expected_frange='1-10',
775+
expected_frames=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
776+
description='tabs around dash'
777+
),
778+
779+
# Newlines (should also be removed)
780+
Case(
781+
input='1\n-\n10',
782+
expected_frange='1-10',
783+
expected_frames=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
784+
description='newlines in range'
785+
),
786+
787+
# Mixed whitespace characters
788+
Case(
789+
input=' \t1\n,\r2 , 3\t ',
790+
expected_frange='1,2,3',
791+
expected_frames=[1, 2, 3],
792+
description='mixed whitespace types'
793+
),
794+
795+
# Edge case: all whitespace
796+
Case(
797+
input=' ',
798+
expected_frange='',
799+
expected_frames=[],
800+
description='only whitespace (empty range)'
801+
),
802+
803+
# Negative frames with whitespace
804+
Case(
805+
input=' -10 - -5 ',
806+
expected_frange='-10--5',
807+
expected_frames=[-10, -9, -8, -7, -6, -5],
808+
description='negative frames with spaces'
809+
),
810+
]
811+
812+
for case in table:
813+
with self.subTest(case.description):
814+
fs = FrameSet(case.input)
815+
816+
# Check normalized frange string
817+
self.assertEqual(fs.frange, case.expected_frange,
818+
f"frange mismatch for {case.description}")
819+
820+
# Check actual frame values
821+
actual_frames = list(fs)
822+
self.assertEqual(actual_frames, case.expected_frames,
823+
f"frames mismatch for {case.description}")
824+
825+
# Check length
826+
self.assertEqual(len(fs), len(case.expected_frames),
827+
f"length mismatch for {case.description}")
828+
660829
class TestBase(unittest.TestCase):
661830
RX_PATHSEP = re.compile(r'[/\\]')
662831

0 commit comments

Comments
 (0)