home · contact · privacy
Streamline exceptions with command option relevance messages.
authorPlom Heller <plom@plomlompom.com>
Sat, 4 Apr 2026 05:59:52 +0000 (07:59 +0200)
committerPlom Heller <plom@plomlompom.com>
Sat, 4 Apr 2026 05:59:52 +0000 (07:59 +0200)
bookmaker.py

index c007d6b7cd6b7b198f0d25e93b9367122626a035..36fe62b97149e6fa808b8340e20afa4302bafffe 100755 (executable)
@@ -52,8 +52,7 @@ class PageCrop:
         zoom_vertical = A4_HEIGHT / (A4_HEIGHT - self.bottom - self.top)
         if zoom_horizontal > 1 > zoom_vertical\
                 or zoom_horizontal < 1 < zoom_vertical:
-            raise HandledException(
-                    '-c: crops would create opposing zoom directions')
+            raise ArgFail('c',  'crops would create opposing zoom directions')
         if zoom_horizontal + zoom_vertical > 2:
             self.zoom = min(zoom_horizontal, zoom_vertical)
         else:
@@ -94,8 +93,13 @@ class Nup4Geometry:
         self.shrink_for_spine = 1 - spine_part_of_page
 
 
-class HandledException(Exception):
-    pass
+class ArgFail(Exception):
+    'Collects relevant command character, followed by explanation.'
+
+    def __init__(self, arg_char, msg, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.arg_char = arg_char
+        self.msg = msg
 
 
 def parse_args():
@@ -161,60 +165,59 @@ def parse_args():
 def validate_inputs_first_pass(args):
     for filename in args.input_file:
         if not os.path.isfile(filename):
-            raise HandledException(f'-i: {filename} is not a file')
+            raise ArgFail('i', f'{filename} is not a file')
         try:
             with open(filename, 'rb') as file:
                 pypdf.PdfReader(file)
         except pypdf.errors.PdfStreamError:
-            raise HandledException(
-                    f'-i: cannot interpret {filename} as PDF file')
+            raise ArgFail('i', f'cannot interpret {filename} as PDF file')
     if args.page_range:
         for p_string in args.page_range:
-            validate_page_range(p_string, '-p')
+            validate_page_range(p_string, 'p')
         if len(args.page_range) > len(args.input_file):
-            raise HandledException(
-                '-p: more --page_range arguments than --input_file arguments')
+            raise ArgFail(
+                    'p',
+                    'more --page_range arguments than --input_file arguments')
     if args.crops:
         for c_string in args.crops:
             initial_split = c_string.split(':')
             if len(initial_split) > 2:
-                raise HandledException(
-                        f'-c: cropping string has multiple ":": {c_string}')
+                raise ArgFail('c',
+                              f'cropping string has multiple ":": {c_string}')
             page_range, crops = split_crops_string(c_string)
             crops = crops.split(',')
             if page_range:
-                validate_page_range(page_range, '-c')
+                validate_page_range(page_range, 'c')
             if len(crops) != 4:
-                raise HandledException(
-                        '-c: cropping does not contain exactly three ",": '
-                        + c_string)
+                raise ArgFail(
+                    'c',
+                    f'cropping does not contain exactly three ",": {c_string}')
             for crop in crops:
                 try:
                     float(crop)
                 except ValueError:
-                    raise HandledException(
-                            f'-c: non-number crop in: {c_string}')
+                    raise ArgFail('c', f'non-number crop in: {c_string}')
     if args.rotate_page:
         for r in args.rotate_page:
             try:
                 int(r)
             except ValueError:
-                raise HandledException(f'-r: non-integer value: {r}')
+                raise ArgFail('r', f'non-integer value: {r}')
             if r < 1:
-                raise HandledException(f'-r: value must not be <1: {r}')
+                raise ArgFail('r', 'value must not be <1: {r}')
     try:
         float(args.print_margin)
     except ValueError:
-        raise HandledException(f'-m: non-float value: {args.print_margin}')
+        raise ArgFail('m', f'non-float value: {args.print_margin}')
 
 
-def validate_page_range(p_string, err_msg_prefix):
-    prefix = f'{err_msg_prefix}: page range string'
+def validate_page_range(p_string, arg_char):
+    prefix = 'page range string'
     if '-' not in p_string:
-        raise HandledException(f'{prefix} lacks "-": {p_string}')
+        raise ArgFail(arg_char, f'{prefix} lacks "-": {p_string}')
     tokens = p_string.split('-')
     if len(tokens) > 2:
-        raise HandledException(f'{prefix} has too many "-": {p_string}')
+        raise ArgFail(arg_char, f'{prefix} has too many "-": {p_string}')
     for i, token in enumerate(tokens):
         if token == '':
             continue
@@ -225,12 +228,12 @@ def validate_page_range(p_string, err_msg_prefix):
         try:
             int(token)
         except ValueError:
-            raise HandledException(
-                    f'{prefix} carries value neither integer, '
-                    f'nor "start", nor "end": {p_string}')
+            raise ArgFail(arg_char,
+                          f'{prefix} carries value neither integer, '
+                          f'nor "start", nor "end": {p_string}')
         if int(token) < 1:
-            raise HandledException(
-                    f'{prefix} carries page number <1: {p_string}')
+            raise ArgFail(arg_char,
+                          f'{prefix} carries page number <1: {p_string}')
     start = -1
     end = -1
     try:
@@ -239,8 +242,8 @@ def validate_page_range(p_string, err_msg_prefix):
     except ValueError:
         pass
     if start > end > 0:
-        raise HandledException(
-                f'{prefix} has higher start than end value: {p_string}')
+        raise ArgFail(arg_char,
+                      f'{prefix} has higher start than end value: {p_string}')
 
 
 def split_crops_string(c_string):
@@ -298,14 +301,15 @@ def validate_inputs_second_pass(args, pages_to_add):
             if page_range:
                 _, end = parse_page_range(page_range, pages_to_add)
                 if end > len(pages_to_add):
-                    raise HandledException(
-                            f'-c: page range goes beyond number of pages '
-                            f'we\'re building: {page_range}')
+                    raise ArgFail('c',
+                                  'page range goes beyond number of pages '
+                                  f'we\'re building: {page_range}')
     if args.rotate_page:
         for r in args.rotate_page:
             if r > len(pages_to_add):
-                raise HandledException('-r: page number beyond number of '
-                                       f'pages we\'re building: {r}')
+                raise ArgFail('r',
+                              'page number beyond number of '
+                              f'pages we\'re building: {r}')
 
 
 def rotate_pages(args_rotate_page, pages_to_add):
@@ -557,8 +561,8 @@ def main():
         try:
             from reportlab.pdfgen.canvas import Canvas
         except ImportError:
-            raise HandledException(
-                    '-n: need reportlab.pdfgen.canvas installed for --nup4')
+            raise ArgFail('n',
+                          'need reportlab.pdfgen.canvas installed for --nup4')
     pages_to_add, opened_files = read_inputs_to_pagelist(args.input_file,
                                                          args.page_range)
     validate_inputs_second_pass(args, pages_to_add)
@@ -588,5 +592,5 @@ def main():
 if __name__ == '__main__':
     try:
         main()
-    except HandledException as e:
-        handled_error_exit(e)
+    except ArgFail as e:
+        handled_error_exit(f'-{e.arg_char}: {e.msg}')