From: Plom Heller Date: Sun, 1 Nov 2026 15:27:08 +0000 (+0100) Subject: Turn page ranges into proper class, move validation there. X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/%22https:/validator.w3.org/task?a=commitdiff_plain;h=65fab4800b522b672827ce3881b2429580aace8c;p=bookmaker Turn page ranges into proper class, move validation there. --- diff --git a/src/bookmaker/misc.py b/src/bookmaker/misc.py index 767c32f..7267ddd 100755 --- a/src/bookmaker/misc.py +++ b/src/bookmaker/misc.py @@ -39,6 +39,61 @@ PAGE_ORDER_FOR_NUP4 = (3, 0, 7, 4, 1, 2, 5, 6) ROTATE_COMMAND = '/Rotate' +_TOK_RANGE_START = 'start' +_TOK_RANGE_END = 'end' + + +class PageRange: + 'Section of multi-page document.' + start = 0 + end: int + + def __init__( + self, + code: Optional[str], + end_max: int = -1 + ) -> None: + if code: + prefix = 'page range string' + if '-' not in code: + raise MsgFail(f'{prefix} lacks "-": {code}') + tokens = code.split('-') + if len(tokens) > 2: + raise MsgFail(f'{prefix} has too many "-": {code}') + for i, token in enumerate(tokens): + if token == '': + continue + if i == 0 and token == _TOK_RANGE_START: + continue + if i == 1 and token == _TOK_RANGE_END: + continue + try: + int(token) + except ValueError as e: + raise MsgFail( + f'{prefix} carries value neither integer, nor ' + f'"{_TOK_RANGE_START}", nor "{_TOK_RANGE_END}": {code}' + ) from e + if int(token) < 1: + raise MsgFail(f'{prefix} carries page number <1: {code}') + str_start, str_end = code.split('-') + self.end = end_max + if str_end not in {'', _TOK_RANGE_END}: + self.end = int(str_end) + if self.end > end_max >= 0: + raise MsgFail( + f'{prefix} exceeds maximum {end_max}: {code}') + if str_start not in {'', _TOK_RANGE_START}: + self.start = int(str_start) - 1 + + def ranging( + self + ) -> range: + 'Turn into range().' + assert self.end >= 0 + return range(self.start, self.end) + + class Page: 'Fuses PdfPage, PageCrop, and PdfTransformation.' @@ -388,8 +443,10 @@ def parse_args( help='output PDF file') parser.add_argument('-p', '--page_range', + type=PageRange, action='append', - help='page range, e.g. "2-9" or "3-end" or "start-14"') + help=(f'page range, e.g. "2-9" or "3-{_TOK_RANGE_END}"' + f'or "{_TOK_RANGE_START}-14"')) parser.add_argument('-c', '--crops', action='append', @@ -437,45 +494,9 @@ def _validate_args_syntax( ) -> None: 'Check command args against general syntax expectations.' - def validate_page_range( - pr_string: str - ) -> None: - prefix = 'page range string' - if '-' not in pr_string: - raise MsgFail(f'{prefix} lacks "-": {pr_string}') - tokens = pr_string.split('-') - if len(tokens) > 2: - raise MsgFail(f'{prefix} has too many "-": {pr_string}') - for i, token in enumerate(tokens): - if token == '': - continue - if i == 0 and token == 'start': - continue - if i == 1 and token == 'end': - continue - try: - int(token) - except ValueError as e: - raise MsgFail(f'{prefix} carries value neither integer, ' - f'nor "start", nor "end": {pr_string}') from e - if int(token) < 1: - raise MsgFail(f'{prefix} carries page number <1: {pr_string}') - start = -1 - end = -1 - try: - start = int(tokens[0]) - end = int(tokens[1]) - except ValueError: - pass - if start > end > 0: - raise MsgFail( - f'{prefix} has higher start than end value: {pr_string}') - try: if args.page_range: arg_char = 'p' - for p_string in args.page_range: - validate_page_range(p_string) if len(args.page_range) > len(args.input_file): raise MsgFail( 'more --page_range arguments than --input_file arguments') @@ -487,9 +508,8 @@ def _validate_args_syntax( raise MsgFail( f'cropping string has multiple ":": {c_string}') page_range, crops_str = split_crops_string(c_string) + PageRange(page_range) crops = crops_str.split(',') - if page_range: - validate_page_range(page_range) if len(crops) != 4: raise MsgFail('cropping does not contain exactly three ' f'",": {c_string}') @@ -508,7 +528,7 @@ def _validate_args_syntax( if r < 1: raise MsgFail('value must not be <1: {r}') except MsgFail as e: - raise ArgFail(arg_char, e.msg) + raise ArgFail(arg_char, e.msg) from e try: float(args.print_margin) except ValueError as e: @@ -529,28 +549,13 @@ def split_crops_string( return page_range, crops -def parse_page_range( - range_string: Optional[str], - idx_after: int - ) -> tuple[int, int]: - 'Based on actual pages size read range_string into range limit indices.' - idx_start = 0 - if range_string: - start, end = range_string.split('-') - if not (len(start) == 0 or start == 'start'): - idx_start = int(start) - 1 - if not (len(end) == 0 or end == 'end'): - idx_after = int(end) - return idx_start, idx_after - - class Book: 'Collection of pages.' def __init__( self, input_filenames: list[str], - args_page_range: list[str], + args_page_range: list[PageRange], do_analyze: bool ) -> None: self.pages: list[Page] = [] @@ -569,11 +574,10 @@ class Book: to_close.close() raise ArgFail('i', f'cannot interpret {path} as PDF file') from e - range_string = None - if args_page_range and len(args_page_range) > i: - range_string = args_page_range[i] - for old_page_num in range(*parse_page_range(range_string, - len(reader.pages))): + for old_page_num in ( + args_page_range[i] if len(args_page_range) > i + else PageRange(None, len(reader.pages)) + ).ranging(): new_page_num += 1 page = (Page.new_blank() if old_page_num >= len(reader.pages) else Page(reader.pages[old_page_num])) @@ -598,11 +602,10 @@ class Book: len_ = len(self) if args_crops: for c_string in args_crops: - if (page_range := split_crops_string(c_string)[0])\ - and parse_page_range(page_range, len_)[1] > len_: - raise ArgFail('c', - 'page range goes beyond number of pages ' - f'we\'re building: {page_range}') + try: + PageRange(split_crops_string(c_string)[0], len_ - 1) + except MsgFail as e: + raise ArgFail('c', e.msg) from e if args_rotate_page: for r in [r for r in args_rotate_page if r > len_]: raise ArgFail('r', @@ -677,17 +680,18 @@ class Book: 'Calculate individual PageCrops from inputs.' if args_crops: for c_string in args_crops: - page_range, crops = split_crops_string(c_string) - idx_start, idx_after = parse_page_range(page_range, len(self)) + range_code, crops = split_crops_string(c_string) + page_range = PageRange(range_code, len(self)) prefix = '-c, -t' if args_symmetry else '-c' suffix = ((' (but alternating left and right crop ' 'between even and odd pages)') if args_symmetry else '') page_crop = PageCrop(*(float(crop) for crop in crops.split(','))) - print(f'{prefix}: to pages {idx_start + 1}:{idx_after} ' - f'applying crop: {page_crop.format_in_cm}{suffix}') - for idx in range(idx_start, idx_after): + print(f'{prefix}: to pages {page_range.start + 1}:' + f'{page_range.end} applying crop: ' + f'{page_crop.format_in_cm}{suffix}') + for idx in page_range.ranging(): self.pages[idx].crop.add(page_crop, args_symmetry and idx % 2) elif args_keep_mediabox: