Repository: Tobi208/pypdf-cli (language: python)

Issue:
Rework page selection
The page selection methods -i -r -l should be combined into a single -p option


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 11a4a93..8065634 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,6 @@ Requires **python >= 3.6**
 ## Commands
 
 ### decrypt
-
 ```
 Usage: pypdf-cli decrypt [OPTIONS] INPUT_FILE
 
@@ -56,11 +55,10 @@ Options:
                      overwrites existing files.
   --password TEXT    The password to match.  [required]
   --help             Show this message and exit.
-```
 
+```
 
 ### delete
-
 ```
 Usage: pypdf-cli delete [OPTIONS] INPUT_FILE
 
@@ -69,19 +67,16 @@ Usage: pypdf-cli delete [OPTIONS] INPUT_FILE
   INPUT_FILE is the location of the pdf file you wish to delete pages from.
 
 Options:
-  -i, --select-index INT        Single index. Enter an integer number.
-  -r, --select-range INT RANGE  Range of indices. Enter as list with 2
-                                elements without spaces or wrap in quotation
-                                marks. E.g. 1-3.
-  -l, --select-list INT LIST    List of indices. Enter list without spaces or
-                                wrap in quotation marks. E.g. 1,2,3.
+  -p, --select-pages INT PAGES  Selection of pages. Enter list of integers and
+                                ranges without spaces or wrap in quotation
+                                marks. E.g. 1,3-5,7.
   -o, --output PATH             Optional location of the output pdf file.
                                 WARNING: overwrites existing files.
   --help                        Show this message and exit.
+
 ```
 
 ### encrypt
-
 ```
 Usage: pypdf-cli encrypt [OPTIONS] INPUT_FILE
 
@@ -101,11 +96,10 @@ Options:
   --use-40bit            Whether to use 40bit encryption. When false, 128bit
                          encryption will be used.
   --help                 Show this message and exit.
-```
 
+```
 
 ### extract
-
 ```
 Usage: pypdf-cli extract [OPTIONS] INPUT_FILE
 
@@ -114,20 +108,16 @@ Usage: pypdf-cli extract [OPTIONS] INPUT_FILE
   INPUT_FILE is the location of the pdf file you wish to extract pages from.
 
 Options:
-  -i, --select-index INT        Single index. Enter an integer number.
-  -r, --select-range INT RANGE  Range of indices. Enter as list with 2
-                                elements without spaces or wrap in quotation
-                                marks. E.g. 1-3.
-  -l, --select-list INT LIST    List of indices. Enter list without spaces or
-                                wrap in quotation marks. E.g. 1,2,3.
+  -p, --select-pages INT PAGES  Selection of pages. Enter list of integers and
+                                ranges without spaces or wrap in quotation
+                                marks. E.g. 1,3-5,7.
   -o, --output PATH             Optional location of the output pdf file.
                                 WARNING: overwrites existing files.
   --help                        Show this message and exit.
-```
 
+```
 
 ### info
-
 ```
 Usage: pypdf-cli info [OPTIONS] INPUT_FILE
 
@@ -137,11 +127,10 @@ Usage: pypdf-cli info [OPTIONS] INPUT_FILE
 
 Options:
   --help  Show this message and exit.
-```
 
+```
 
 ### insert
-
 ```
 Usage: pypdf-cli insert [OPTIONS] INPUT_FILES...
 
@@ -153,14 +142,15 @@ Usage: pypdf-cli insert [OPTIONS] INPUT_FILES...
   the first.
 
 Options:
-  -o, --output PATH       Optional location of the output pdf file. WARNING:
-                          overwrites existing files.
-  -i, --select-index INT  Single index. Enter an integer number.  [required]
-  --help                  Show this message and exit.
+  -o, --output PATH             Optional location of the output pdf file.
+                                WARNING: overwrites existing files.
+  -p, --select-pages INT PAGES  Selection of page. Enter integer. E.g. 2.
+                                [required]
+  --help                        Show this message and exit.
+
 ```
 
 ### merge
-
 ```
 Usage: pypdf-cli merge [OPTIONS] [INPUT_FILES]...
 
@@ -173,11 +163,10 @@ Options:
   -o, --output PATH  Optional location of the output pdf file. WARNING:
                      overwrites existing files.
   --help             Show this message and exit.
-```
 
+```
 
 ### remove
-
 ```
 Usage: pypdf-cli remove [OPTIONS] INPUT_FILE
 
@@ -193,11 +182,10 @@ Options:
   --links            Whether to remove links.
   --text             Whether to remove text.
   --help             Show this message and exit.
-```
 
+```
 
 ### reverse
-
 ```
 Usage: pypdf-cli reverse [OPTIONS] INPUT_FILE
 
@@ -209,11 +197,10 @@ Options:
   -o, --output PATH  Optional location of the output pdf file. WARNING:
                      overwrites existing files.
   --help             Show this message and exit.
-```
 
+```
 
 ### rotate
-
 ```
 Usage: pypdf-cli rotate [OPTIONS] INPUT_FILE
 
@@ -222,22 +209,19 @@ Usage: pypdf-cli rotate [OPTIONS] INPUT_FILE
   INPUT_FILE is the location of the pdf file you wish to rotate.
 
 Options:
-  -i, --select-index INT        Single index. Enter an integer number.
-  -r, --select-range INT RANGE  Range of indices. Enter as list with 2
-                                elements without spaces or wrap in quotation
-                                marks. E.g. 1-3.
-  -l, --select-list INT LIST    List of indices. Enter list without spaces or
-                                wrap in quotation marks. E.g. 1,2,3.
+  -p, --select-pages INT PAGES  Selection of pages. Enter list of integers and
+                                ranges without spaces or wrap in quotation
+                                marks. E.g. 1,3-5,7.
   -o, --output PATH             Optional location of the output pdf file.
                                 WARNING: overwrites existing files.
   -a, --all                     Select every index.
   --angle INTEGER               Angle to rotate pages clockwise. Must be
                                 increment of 90.  [required]
   --help                        Show this message and exit.
+
 ```
 
 ### scale
-
 ```
 Usage: pypdf-cli scale [OPTIONS] INPUT_FILE
 
@@ -247,12 +231,9 @@ Usage: pypdf-cli scale [OPTIONS] INPUT_FILE
   INPUT_FILE is the location of the pdf file you wish to scale.
 
 Options:
-  -i, --select-index INT        Single index. Enter an integer number.
-  -r, --select-range INT RANGE  Range of indices. Enter as list with 2
-                                elements without spaces or wrap in quotation
-                                marks. E.g. 1-3.
-  -l, --select-list INT LIST    List of indices. Enter list without spaces or
-                                wrap in quotation marks. E.g. 1,2,3.
+  -p, --select-pages INT PAGES  Selection of pages. Enter list of integers and
+                                ranges without spaces or wrap in quotation
+                                marks. E.g. 1,3-5,7.
   -o, --output PATH             Optional location of the output pdf file.
                                 WARNING: overwrites existing files.
   -a, --all                     Select every index.
@@ -263,10 +244,10 @@ Options:
   --vertical FLOAT              Vertical factor or value to scale pages by or
                                 to.  [required]
   --help                        Show this message and exit.
+
 ```
 
 ### split
-
 ```
 Usage: pypdf-cli split [OPTIONS] INPUT_FILE
 
@@ -279,14 +260,12 @@ Usage: pypdf-cli split [OPTIONS] INPUT_FILE
   INPUT_FILE is the location of the pdf file you want to split.
 
 Options:
-  -i, --select-index INT        Single index. Enter an integer number.
-  -r, --select-range INT RANGE  Range of indices. Enter as list with 2
-                                elements without spaces or wrap in quotation
-                                marks. E.g. 1-3.
-  -l, --select-list INT LIST    List of indices. Enter list without spaces or
-                                wrap in quotation marks. E.g. 1,2,3.
+  -p, --select-pages INT PAGES  Selection of pages. Enter list of integers and
+                                ranges without spaces or wrap in quotation
+                                marks. E.g. 1,3-5,7.
   -o, --output PATH             Optional location of the output pdf file.
                                 WARNING: overwrites existing files.
   -a, --all                     Select every index.
   --help                        Show this message and exit.
+
 ```
diff --git a/src/pypdf_cli.py b/src/pypdf_cli.py
index a343bee..7103dae 100644
--- a/src/pypdf_cli.py
+++ b/src/pypdf_cli.py
@@ -36,24 +36,13 @@ def generate_output_name(input_name, output_name, default):
     return basename(input_name)[:-4] + '_' + default + '.pdf'
 
 
-def generate_selection(selection_collection, validation, non_empty=True):
+def validate_selection(selection, validation, non_empty=True):
     """
-    Verify selection and sum selections up.
+    Validate selection given a selection and validation method.
     """
 
-    def append_selection(result, xs):
-        if isinstance(xs, set):
-            result |= xs
-        else:
-            for x in xs:
-                append_selection(result, x)
-
-    # recursively append selections
-    selection = set()
-    append_selection(selection, selection_collection)
-
     # verify existence of any selection if required
-    if non_empty and len(selection) == 0:
+    if non_empty and (selection is None or len(selection) == 0):
         raise click.BadParameter(message='No pages selected.')
 
     # verify selection by provided function
@@ -119,55 +108,31 @@ def cli():
 # Make custom Option classes to parse input
 # Decrement user indices to match PyPDF indices
 
-class ListType(click.ParamType):
-    name = "INT LIST"
-
-    def convert(self, value, param, ctx):
-
-        try:
-            selection = set(int(v) - 1 for v in value.split(','))
-        except ValueError:
-            self.fail(f'{value!r} is not a valid list of integers. Use as 1,2,3,4.', param, ctx)
-
-        return selection
-
-
-class RangeType(click.ParamType):
-    name = "INT RANGE"
+class PagesType(click.ParamType):
+    
+    name = "INT PAGES"
 
     def convert(self, value, param, ctx):
 
         try:
-            splits = value.split('-')
-            assert len(splits) == 2
-            selection = set(range(int(splits[0]) - 1, int(splits[1])))
+            selection = set()
+            for c_split in value.replace(' ', '').split(','):
+                if '-' in c_split:
+                    r_split = c_split.split('-')
+                    assert len(r_split) == 2
+                    selection.update(range(int(r_split[0]) - 1, int(r_split[1])))
+                else:
+                    selection.add(int(c_split) - 1)
         except (ValueError, AssertionError):
-            self.fail(f'{value!r} is not a valid range of integers. Use as 1-4.', param, ctx)
-
-        return selection
-
-
-class IntType(click.ParamType):
-    name = "INT"
-
-    def convert(self, value, param, ctx):
-
-        try:
-            selection = {int(value) - 1}
-        except ValueError:
-            self.fail(f'{value!r} is not a valid integer.', param, ctx)
+            self.fail(f'{value!r} is not a valid selection of integers. Use as 1,3-5,7.', param, ctx)
 
         return selection
 
-
-LIST_TYPE = ListType()
-RANGE_TYPE = RangeType()
-INT_TYPE = IntType()
+INT_PAGES = PagesType()
 
 OUTPUT_HELP = 'Optional location of the output pdf file. WARNING: overwrites existing files.'
-LIST_HELP = 'List of indices. Enter list without spaces or wrap in quotation marks. E.g. 1,2,3.'
-RANGE_HELP = 'Range of indices. Enter as list with 2 elements without spaces or wrap in quotation marks. E.g. 1-3.'
-INDEX_HELP = 'Single index. Enter an integer number.'
+PAGES_MULTI_HELP = 'Selection of pages. Enter list of integers and ranges without spaces or wrap in quotation marks. E.g. 1,3-5,7.'
+PAGES_SINGLE_HELP = 'Selection of page. Enter integer. E.g. 2.'
 ALL_HELP = 'Select every index.'
 
 
@@ -178,9 +143,7 @@ def common_options(f):
 
     f = click.argument('input-file', type=click.Path(exists=True))(f)
     f = click.option('--output', '-o', type=click.Path(dir_okay=True), help=OUTPUT_HELP)(f)
-    f = click.option('--select-list', '-l', type=LIST_TYPE, multiple=True, help=LIST_HELP)(f)
-    f = click.option('--select-range', '-r', type=RANGE_TYPE, multiple=True, help=RANGE_HELP)(f)
-    f = click.option('--select-index', '-i', type=INT_TYPE, multiple=True, help=INDEX_HELP)(f)
+    f = click.option('--select-pages', '-p', type=INT_PAGES, multiple=False, help=PAGES_MULTI_HELP)(f)
     return f
 
 
@@ -188,7 +151,7 @@ def common_options(f):
 
 @click.command()
 @common_options
-def delete(input_file, output, select_list, select_range, select_index):
+def delete(input_file, output, select_pages):
     """
     Delete a selection of pages from a pdf file.
 
@@ -198,7 +161,7 @@ def delete(input_file, output, select_list, select_range, select_index):
     # configure and verify
     output = generate_output_name(input_file, output, 'deleted')
     reader = PdfReader(open(input_file, 'rb'))
-    selection = generate_selection([select_list, select_range, select_index], validate_index(len(reader.pages)))
+    selection = validate_selection(select_pages, validate_index(len(reader.pages)))
     writer = PdfWriter()
 
     # specific verification
@@ -218,7 +181,7 @@ def delete(input_file, output, select_list, select_range, select_index):
 
 @click.command()
 @common_options
-def extract(input_file, output, select_list, select_range, select_index):
+def extract(input_file, output, select_pages):
     """
     Extract a selection of pages from a pdf file.
 
@@ -231,7 +194,7 @@ def extract(input_file, output, select_list, select_range, select_index):
         reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    selection = generate_selection([select_list, select_range, select_index], validate_index(len(reader.pages)))
+    selection = validate_selection(select_pages, validate_index(len(reader.pages)))
     writer = PdfWriter()
 
     # specific verification
@@ -251,8 +214,8 @@ def extract(input_file, output, select_list, select_range, select_index):
 @click.command()
 @click.argument('input-files', nargs=2, type=click.Path(exists=True))
 @click.option('--output', '-o', type=click.Path(), help=OUTPUT_HELP)
-@click.option('--select-index', '-i', type=INT_TYPE, required=True, help=INDEX_HELP)
-def insert(input_files, output, select_index):
+@click.option('--select-pages', '-p', type=INT_PAGES, required=True, multiple=False, help=PAGES_SINGLE_HELP)
+def insert(input_files, output, select_pages):
     """
     Insert a second pdf file into a pdf file at a specified index.
     The new pdf file will contain the second file's pages starting at the index.
@@ -268,8 +231,8 @@ def insert(input_files, output, select_index):
         reader2 = PdfReader(open(input_files[1], 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-
-    index = generate_selection([set(select_index)], validate_index(len(reader1.pages))).pop()
+    selection = validate_selection(select_pages, validate_index(len(reader1.pages)))
+    index = selection.pop()
     writer = PdfWriter()
 
     # build output
@@ -321,7 +284,7 @@ def merge(input_files, output):
 @click.command()
 @common_options
 @click.option('--all', '-a', is_flag=True, default=False, help=ALL_HELP)
-def split(input_file, output, select_list, select_range, select_index, all):
+def split(input_file, output, select_pages, all):
     """
     Split a pdf file at specified indices.
     The file is split AFTER a specified index. E.g. pages = {1, 2, 3, 4} and indices = [2, 3]
@@ -347,9 +310,11 @@ def split(input_file, output, select_list, select_range, select_index, all):
 
     # if 'all' flag is set split at every index
     # requires selection to be a sorted list
-    selection = list(range(len(reader.pages) - 1)) if all else sorted(list(
-        generate_selection([select_list, select_range, select_index], validate_index(len(reader.pages), rshift=1))
-    ))
+    if all:
+        selection = list(range(len(reader.pages) - 1))
+    else:
+        selection = validate_selection(select_pages, validate_index(len(reader.pages), rshift=1))
+        selection = sorted(list(selection))
 
     # calculate splits that will make up the outputs
     splits = []
@@ -555,7 +520,7 @@ def reverse(input_file, output):
 @click.option('--all', '-a', is_flag=True, default=False, help=ALL_HELP)
 @click.option('--angle', type=click.INT, required=True,
               help='Angle to rotate pages clockwise. Must be increment of 90.')
-def rotate(input_file, output, select_list, select_range, select_index, all, angle):
+def rotate(input_file, output, select_pages, all, angle):
     """
     Rotate a selection of pages of a pdf file.
 
@@ -571,9 +536,10 @@ def rotate(input_file, output, select_list, select_range, select_index, all, ang
     writer = PdfWriter()
 
     # if 'all' flag is set rotate every page
-    selection = list(range(len(reader.pages))) if all else list(
-        generate_selection([select_list, select_range, select_index], validate_index(len(reader.pages)))
-    )
+    if all:
+        selection = set(range(len(reader.pages)))
+    else:
+        selection = validate_selection(select_pages, validate_index(len(reader.pages)))
 
     # specific verification
     if angle % 90 != 0:
@@ -599,7 +565,7 @@ def rotate(input_file, output, select_list, select_range, select_index, all, ang
               help='Horizontal factor or value to scale pages by or to.')
 @click.option('--vertical', type=click.FLOAT, required=True,
               help='Vertical factor or value to scale pages by or to.')
-def scale(input_file, output, select_list, select_range, select_index, all, scale_to, horizontal, vertical):
+def scale(input_file, output, select_pages, all, scale_to, horizontal, vertical):
     """
     Scale a selection of pages of a pdf file.
     Uses scaleBy by default. Use --scale-to to scale to a flat value.
@@ -616,9 +582,10 @@ def scale(input_file, output, select_list, select_range, select_index, all, scal
     writer = PdfWriter()
 
     # if 'all' flag is set rotate every page
-    selection = list(range(len(reader.pages))) if all else list(
-        generate_selection([select_list, select_range, select_index], validate_index(len(reader.pages)))
-    )
+    if all:
+        selection = set(range(len(reader.pages)))
+    else:
+        selection = validate_selection(select_pages, validate_index(len(reader.pages)))
 
     # build output
     writer.append_pages_from_reader(reader)


Test patch:
diff --git a/tests/test_pypdf-cli.py b/tests/test_pypdf-cli.py
index 69fbd19..9b49eaf 100644
--- a/tests/test_pypdf-cli.py
+++ b/tests/test_pypdf-cli.py
@@ -39,7 +39,7 @@ def test_generate_output_name():
     assert generate_output_name('dir/in put.pdf', None, '') == 'in put_.pdf'
 
 
-def test_generate_selection():
+def test_validate_selection():
     def true(_):
         return True
 
@@ -50,33 +50,24 @@ def even(x):
         return x % 2 == 0
 
     with pytest.raises(click.BadParameter):
-        generate_selection([], true)
+        validate_selection(set(), true)
     with pytest.raises(click.BadParameter):
-        generate_selection([set()], true)
+        validate_selection(set(), false)
     with pytest.raises(click.BadParameter):
-        generate_selection([set(), set()], true)
+        validate_selection({1}, false)
     with pytest.raises(click.BadParameter):
-        generate_selection([], false)
+        validate_selection({1, 2, 3}, even)
     with pytest.raises(click.BadParameter):
-        generate_selection([{1}], false)
-    with pytest.raises(click.BadParameter):
-        generate_selection([{1, 2, 3}], even)
-    with pytest.raises(click.BadParameter):
-        generate_selection([{2, 4}, {5}], even)
-    with pytest.raises(click.BadParameter):
-        generate_selection([{1, 2}], even)
-    with pytest.raises(click.BadParameter):
-        generate_selection([], even)
+        validate_selection(set(), even)
 
-    assert generate_selection([], true, non_empty=False) == set()
-    assert generate_selection([{1}], true) == {1}
-    assert generate_selection([{1}, {2}], true) == {1, 2}
-    assert generate_selection([{1, 2}, {3}], true) == {1, 2, 3}
-    assert generate_selection([{1, 2}, set()], true) == {1, 2}
-    assert generate_selection([{2}], even) == {2}
-    assert generate_selection([{2, 4}], even) == {2, 4}
-    assert generate_selection([{2}, set()], even) == {2}
-    assert generate_selection([[{2, 4}, {8}], {6}], even) == {2, 4, 6, 8}
+    try: 
+        validate_selection(set(), true, non_empty=False)
+        validate_selection({1}, true)
+        validate_selection({1, 2}, true)
+        validate_selection({2}, even)
+        validate_selection({2, 4}, even)
+    except click.BadParameter:
+        pytest.fail("Unexpect BadParameter Error")
 
 
 def test_validate_index():
@@ -124,96 +115,106 @@ def test_buffer_number():
     assert buffer_number(0, 1) == '1'
 
 
-def test_convert_list():
+def test_convert_pages():
+    # result.exit_code = 0 -> okay
+    # result.exit_code = 2 -> error raised
+    
     result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT])
     assert result.exit_code == 2
-
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-l', '1,a'])
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-l', '1,2.0'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,a'])
+    assert result.exit_code == 2
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2.0'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-l', '1'])
-    assert result.exit_code == 0
-
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-l', '1,2,3'])
-    assert result.exit_code == 0
-
-
-def test_convert_range():
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1-a'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-a'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1-2.0'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-2.0'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1-2-3'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-2-3'])
+    assert result.exit_code == 2
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2-a'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1,2'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2-'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1-2'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1'])
+    assert result.exit_code == 0
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2,3'])
+    assert result.exit_code == 0
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-2'])
+    assert result.exit_code == 0
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-2,4-5'])
     assert result.exit_code == 0
 
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,3-5'])
+    assert result.exit_code == 0
 
-def test_convert_int():
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-i', 'a'])
-    assert result.exit_code == 2
-
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-i', '2.0'])
-    assert result.exit_code == 2
-
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-i', '1'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-3,5'])
+    assert result.exit_code == 0
+    
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,3-5,7'])
     assert result.exit_code == 0
 
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-3,5,7-9'])
+    assert result.exit_code == 0
 
 def test_delete():
     # result.exit_code = 0 -> okay
     # result.exit_code = 2 -> error raised
 
-    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-i', '1'])
+    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(len(res_reader.pages)):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 2}')
 
-    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-l', '1,2,3,4'])
+    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2,3,4'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(len(res_reader.pages)):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 5}')
 
-    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '10-12'])
+    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '10-12'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(len(res_reader.pages)):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
 
-    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '10-12', '-l', '1,2,3,4'])
+    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2,3,4,10-12'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(len(res_reader.pages)):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 5}')
 
-    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '5-10'])
+    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '5-10'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(4):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(4, 6):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 7}')
 
-    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '5-10', '-i', '1'])
+    RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,5-10'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(3):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 2}')
     for i in range(3, 5):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 8}')
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '1-12'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1-12'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-r', '2-12', '-i', '1'])
+    result = RUNNER.invoke(delete, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2-12'])
     assert result.exit_code == 2
 
 
@@ -221,24 +222,24 @@ def test_extract():
     # result.exit_code = 0 -> okay
     # result.exit_code = 2 -> error raised
 
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-i', '1'])
+    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '1'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     assert len(res_reader.pages) == 1
     assert res_reader.pages[0].extract_text().startswith('page1')
 
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-l', '1,2,3,4'])
+    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2,3,4'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     assert len(res_reader.pages) == 4
     for i in range(len(res_reader.pages)):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
 
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '10-12'])
+    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '10-12'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     assert len(res_reader.pages) == 3
     for i in range(len(res_reader.pages)):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 10}')
 
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '10-12', '-l', '1,2,3,4'])
+    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2,3,4,10-12'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     assert len(res_reader.pages) == 7
     for i in range(4):
@@ -246,39 +247,43 @@ def test_extract():
     for i in range(4, 7):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 6}')
 
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '5-10', '-i', '1'])
+    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '1,5-10'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     assert len(res_reader.pages) == 7
     assert res_reader.pages[0].extract_text().startswith('page1')
     for i in range(1, 7):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 4}')
 
-    result = RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '1-12'])
+    result = RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '1-12'])
     assert result.exit_code == 2
 
-    result = RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '2-12', '-i', '1'])
+    result = RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-p', '1,2-12'])
     assert result.exit_code == 2
 
 
 def test_insert():
-    # result.exit_code = 0 -> okay
-    # result.exit_code = 2 -> error raised
-
-    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-i', '1'])
+    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-p', '1'])
+    res_reader = PdfReader(open(TEST_OUT, 'rb'))
+    for i in range(0, 12):
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
+    for i in range(12, 24):
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
+    
+    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-p', '1,2-5'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(0, 12):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(12, 24):
         assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
 
-    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-i', '13'])
+    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-p', '13'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(0, 12):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(12, 24):
         assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
 
-    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-i', '6'])
+    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-p', '6'])
     res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(0, 5):
         assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
@@ -313,7 +318,7 @@ def test_merge():
 
 
 def test_split():
-    RUNNER.invoke(split, [TEST_FILE, '-o', TEST_OUT, '-i', '3'])
+    RUNNER.invoke(split, [TEST_FILE, '-o', TEST_OUT, '-p', '3'])
     test_out_base = TEST_OUT[:-4]
     res1 = PdfReader(open(test_out_base + '_1.pdf', 'rb'))
     for i in range(0, 3):
@@ -322,7 +327,7 @@ def test_split():
     for i in range(0, 9):
         assert res2.pages[i].extract_text().startswith(f'page{i + 4}')
 
-    RUNNER.invoke(split, [TEST_FILE, '-o', TEST_OUT, '-l', '3,9'])
+    RUNNER.invoke(split, [TEST_FILE, '-o', TEST_OUT, '-p', '3,9'])
     test_out_base = TEST_OUT[:-4]
     res1 = PdfReader(open(test_out_base + '_1.pdf', 'rb'))
     for i in range(0, 3):
@@ -340,7 +345,7 @@ def test_split():
         res = PdfReader(open(test_out_base + f'_{buffer_number(2, i)}.pdf', 'rb'))
         assert res.pages[0].extract_text().startswith(f'page{i}')
 
-    result = RUNNER.invoke(split, [test_out_base + '_01.pdf', '-o', TEST_OUT, '-i', '1'])
+    result = RUNNER.invoke(split, [test_out_base + '_01.pdf', '-o', TEST_OUT, '-p', '1'])
     assert result.exit_code == 2
 
 
@@ -385,7 +390,7 @@ def test_remove():
         assert res.pages[i].extract_text().strip() == ''
 
     # I don't know how to test removal of images and links
-    # PyPDF4 doesn't appear to have a high level api for that
+    # pypdf doesn't appear to have a high level api for that
 
 
 def test_info():
@@ -420,7 +425,7 @@ def test_rotate():
         current_angle = rotate_obj if isinstance(rotate_obj, int) else rotate_obj.getObject()
         assert current_angle == -90
 
-    RUNNER.invoke(rotate, [TEST_FILE, '-o', TEST_OUT, '-r', '1-6', '--angle', 90])
+    RUNNER.invoke(rotate, [TEST_FILE, '-o', TEST_OUT, '-p', '1-6', '--angle', 90])
     res = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(6):
         page = res.pages[i]
@@ -441,7 +446,7 @@ def test_scale():
     result = RUNNER.invoke(scale, [TEST_FILE, '-o', TEST_OUT, '-a', '--vertical', 2.0, '--horizontal', 2.0])
     assert result.exit_code == 0
 
-    result = RUNNER.invoke(scale, [TEST_FILE, '-o', TEST_OUT, '-r', '1-6', '--vertical', 2.0, '--horizontal', 2.0])
+    result = RUNNER.invoke(scale, [TEST_FILE, '-o', TEST_OUT, '-p', '1-6', '--vertical', 2.0, '--horizontal', 2.0])
     assert result.exit_code == 0
 
     result = RUNNER.invoke(scale, [TEST_FILE, '-o', TEST_OUT, '-a', '--vertical', 256.0, '--horizontal', 256.0,
@@ -449,4 +454,4 @@ def test_scale():
     assert result.exit_code == 0
 
     # I don't know how to test the size of pages
-    # PyPDF4 doesn't appear to have a high level api for that
+    # pypdf doesn't appear to have a high level api for that