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/.gitignore b/.gitignore
index 1bb4892..c963965 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,4 +103,6 @@ dmypy.json
 .pyre/
 .pytype/
 cython_debug/
-tests/test*.pdf
\ No newline at end of file
+tests/test*.pdf
+
+.vscode/
\ No newline at end of file
diff --git a/README.md b/README.md
index 11a4a93..e1cdf9c 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
 
 ------------
 
-This command line tool is based on [click](https://github.com/pallets/click) and [PyPDF4](https://github.com/claird/PyPDF4).
-It allows access to most of PyPDF4's functionalities and adds other sensible functionalities.
+This command line tool is based on [click](https://github.com/pallets/click) and [pypdf](https://github.com/py-pdf/pypdf).
+It allows access to most of pypdf's functionalities and adds other sensible functionalities.
 The aim is to provide an OS-independent CLI that allows for comfortable every-day PDF manipulation.
 
 ## Download
@@ -12,9 +12,7 @@ Requires **python >= 3.6**
 
 * Build locally:
   * Clone this repository
-  * ``python -m pip -r requirements.txt``
-  * test with ``pytest``
-  * ``python -m build``
+  * ``python -m pip install .``
 
 * Install from [PyPi](https://pypi.org/project/pypdf-cli/):
   * ``python -m pip install pypdf-cli``
@@ -43,7 +41,6 @@ Requires **python >= 3.6**
 ## Commands
 
 ### decrypt
-
 ```
 Usage: pypdf-cli decrypt [OPTIONS] INPUT_FILE
 
@@ -56,11 +53,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 +65,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 +94,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 +106,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 +125,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 +140,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 +161,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 +180,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 +195,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 +207,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 +229,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 +242,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 +258,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/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..dcdfd0f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,35 @@
+[build-system]
+requires = [ "flit_core >=3.2,<4" ]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pypdf-cli"
+version = "1.0.0"
+authors = [ { name = "Tobias Lass", email = "tobi208@github.com" } ]
+description = "A Python-based CLI that allows for comfortable every-day PDF manipulation with pypdf."
+readme = "README.md"
+classifiers = [
+    "Programming Language :: Python :: 3",
+    "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+    "Operating System :: OS Independent",
+]
+requires-python = ">=3.6"
+dependencies = [
+    "click>=8.0.3",
+    "pypdf>=3.16.1",
+]
+license = { file = "LICENSE" }
+keywords = [ "pdf", "cli", "pypdf", "click" ]
+
+[project.urls]
+"Homepage" = "https://github.com/tobi208/pypdf-cli"
+"Bug Tracker" = "https://github.com/tobi208/pypdf-cli/issues"
+
+[project.scripts]
+pypdf-cli = "pypdf_cli:cli"
+
+[project.optional-dependencies]
+test = [ "pytest>=7.0.0" ]
+
+[tool.pytest.ini_options]
+pythonpath = [ "src" ]
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 991eeac..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-click>=8.0.3
-PyPDF4>=1.27.0
-pytest~=6.2.5
-setuptools~=57.0.0
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100644
index fc7b2b0..0000000
--- a/setup.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from setuptools import setup
-
-with open("README.md", "r", encoding="utf-8") as fh:
-    long_description = fh.read()
-
-setup(
-    name='pypdf-cli',
-    version='0.1.0',
-    author='Tobias Lass',
-    author_email='tobi208@github.com',
-    description='A Python-based CLI that allows for comfortable every-day PDF manipulation with PyPDF4.',
-    long_description=long_description,
-    long_description_content_type="text/markdown",
-    url="https://github.com/tobi208/pypdf-cli",
-    project_urls={
-        "Bug Tracker": "https://github.com/tobi208/pypdf-cli/issues",
-    },
-    classifiers=[
-        "Programming Language :: Python :: 3",
-        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
-        "Operating System :: OS Independent",
-    ],
-    license='GNU General Public License v3 (GPLv3)',
-    keywords=['pdf', 'cli', 'pypdf', 'click'],
-    python_requires=">=3.6",
-    install_requires=[
-        'click>=8.0.3',
-        'PyPDF4>=1.27.0',
-    ],
-    package_dir={"": "src"},
-    py_modules=['pypdfcli'],
-    entry_points={
-        'console_scripts': [
-            'pypdf-cli = pypdfcli:cli',
-        ],
-    },
-)
diff --git a/src/pypdfcli.py b/src/pypdf_cli.py
similarity index 70%
rename from src/pypdfcli.py
rename to src/pypdf_cli.py
index 20bede6..3d51d20 100644
--- a/src/pypdfcli.py
+++ b/src/pypdf_cli.py
@@ -1,9 +1,9 @@
-from os import makedirs
+from os import makedirs, listdir, getcwd
 from os.path import basename, dirname
 
 import click
-from PyPDF4 import PdfFileReader, PdfFileWriter, PdfFileMerger
-from PyPDF4.utils import PdfReadError
+from pypdf import PdfReader, PdfWriter
+from pypdf.errors import PdfReadError
 from pkg_resources import get_distribution
 
 
@@ -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)
+            self.fail(f'{value!r} is not a valid selection of integers. Use as 1,3-5,7.', 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)
-
-        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.
 
@@ -197,19 +160,19 @@ def delete(input_file, output, select_list, select_range, select_index):
 
     # configure and verify
     output = generate_output_name(input_file, output, 'deleted')
-    reader = PdfFileReader(open(input_file, 'rb'))
-    selection = generate_selection([select_list, select_range, select_index], validate_index(reader.numPages))
-    writer = PdfFileWriter()
+    reader = PdfReader(open(input_file, 'rb'))
+    selection = validate_selection(select_pages, validate_index(len(reader.pages)))
+    writer = PdfWriter()
 
     # specific verification
-    retain = [i for i in range(reader.numPages) if i not in selection]
+    retain = [i for i in range(len(reader.pages)) if i not in selection]
     if len(retain) == 0:
         reader.stream.close()
         raise click.BadParameter(message='Cannot delete all pages.')
 
     # build output
     for i in retain:
-        writer.addPage(reader.getPage(i))
+        writer.add_page(reader.pages[i])
 
     # finalize
     write(writer, output)
@@ -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.
 
@@ -228,20 +191,20 @@ def extract(input_file, output, select_list, select_range, select_index):
     # configure and verify
     output = generate_output_name(input_file, output, 'extracted')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        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(reader.numPages))
-    writer = PdfFileWriter()
+    selection = validate_selection(select_pages, validate_index(len(reader.pages)))
+    writer = PdfWriter()
 
     # specific verification
-    if len(selection) == reader.numPages:
+    if len(selection) == len(reader.pages):
         reader.stream.close()
         raise click.BadParameter(message='Cannot extract all pages.')
 
     # build output
     for i in selection:
-        writer.addPage(reader.getPage(i))
+        writer.add_page(reader.pages[i])
 
     # finalize
     write(writer, output)
@@ -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.
@@ -264,21 +227,21 @@ def insert(input_files, output, select_index):
     # configure and verify
     output = generate_output_name(input_files[0], output, 'inserted')
     try:
-        reader1 = PdfFileReader(open(input_files[0], 'rb'))
-        reader2 = PdfFileReader(open(input_files[1], 'rb'))
+        reader1 = PdfReader(open(input_files[0], 'rb'))
+        reader2 = PdfReader(open(input_files[1], 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-
-    index = generate_selection([set(select_index)], validate_index(reader1.numPages)).pop()
-    writer = PdfFileWriter()
+    selection = validate_selection(select_pages, validate_index(len(reader1.pages)))
+    index = selection.pop()
+    writer = PdfWriter()
 
     # build output
     for i in range(0, index):
-        writer.addPage(reader1.getPage(i))
+        writer.add_page(reader1.pages[i])
     for page in reader2.pages:
-        writer.addPage(page)
-    for i in range(index, reader1.numPages):
-        writer.addPage(reader1.getPage(i))
+        writer.add_page(page)
+    for i in range(index, len(reader1.pages)):
+        writer.add_page(reader1.pages[i])
 
     # finalize
     write(writer, output)
@@ -287,41 +250,48 @@ def insert(input_files, output, select_index):
 
 
 @click.command()
-@click.argument('input-files', nargs=-1, type=click.Path(exists=True))
+@click.argument('input-files', nargs=-1, type=click.Path(exists=True), required=False)
 @click.option('--output', '-o', type=click.Path(), help=OUTPUT_HELP)
-def merge(input_files, output):
+@click.option('--all', '-a', is_flag=True, default=False, help=ALL_HELP)
+def merge(input_files, output, all):
     """
     Merge two or more pdf files.
     Files are appended in the order they are entered.
 
     INPUT_FILES are the locations of at least two pdf files to be merged.
+    Merge all files in the current directory if no input is given.
     """
 
+    # if no input files are specified or 'all' flag is set,
+    # take all pdf files in current directory
+    if len(input_files) == 0 or all:
+        input_files = [f for f in listdir(getcwd()) if f.endswith('.pdf')]
+
     # specific verification
     if len(input_files) < 2:
         raise click.BadParameter(message='Cannot merge less than two files.')
 
     # configure
     output = generate_output_name(input_files[0], output, 'merged')
-    merger = PdfFileMerger()
+    writer = PdfWriter()
 
     # build output
     for file in input_files:
         try:
-            merger.append(open(file, 'rb'))
+            writer.append(open(file, 'rb'))
         except PdfReadError:
-            merger.close()
+            writer.close()
             raise click.BadParameter('File cannot be read.')
 
     # finalize
-    write(merger, output)
-    merger.close()
+    write(writer, output)
+    writer.close()
 
 
 @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]
@@ -336,20 +306,22 @@ def split(input_file, output, select_list, select_range, select_index, all):
     output = generate_output_name(input_file, output, 'split')
     output = output[:-4] + '_'
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
 
     # specific verification
-    if reader.numPages < 2:
+    if len(reader.pages) < 2:
         reader.stream.close()
         raise click.BadParameter(message="Cannot split a file with less than two pages.")
 
     # if 'all' flag is set split at every index
     # requires selection to be a sorted list
-    selection = list(range(reader.numPages - 1)) if all else sorted(list(
-        generate_selection([select_list, select_range, select_index], validate_index(reader.numPages, 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 = []
@@ -362,7 +334,7 @@ def split(input_file, output, select_list, select_range, select_index, all):
         )
 
     # include pages from last selection to end of pages
-    splits.append(list(range(selection[-1] + 1, reader.numPages)))
+    splits.append(list(range(selection[-1] + 1, len(reader.pages))))
     reader.stream.close()
 
     # track number of splits for output names
@@ -373,10 +345,10 @@ def split(input_file, output, select_list, select_range, select_index, all):
     # recreate reader for each writer because of a PyPDF bug
     # https://stackoverflow.com/questions/40168027
     for pages in splits:
-        writer = PdfFileWriter()
-        reader = PdfFileReader(open(input_file, 'rb'))
+        writer = PdfWriter()
+        reader = PdfReader(open(input_file, 'rb'))
         for p in pages:
-            writer.addPage(reader.getPage(p))
+            writer.add_page(reader.pages[p])
         write(writer, output + buffer_number(max_digits, k) + '.pdf')
         k += 1
         reader.stream.close()
@@ -403,19 +375,19 @@ def encrypt(input_file, output, user_password, owner_password, use_40bit):
     # configure and verify
     output = generate_output_name(input_file, output, 'encrypted')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    writer = PdfFileWriter()
+    writer = PdfWriter()
 
     # specific verification
-    if reader.isEncrypted:
+    if reader.is_encrypted:
         reader.stream.close()
         raise click.BadArgumentUsage(message='File is already encrypted.')
 
     # build output
-    writer.appendPagesFromReader(reader)
-    writer.encrypt(user_pwd=user_password, owner_pwd=owner_password, use_128bit=not use_40bit)
+    writer.append_pages_from_reader(reader)
+    writer.encrypt(user_password=user_password, owner_password=owner_password, use_128bit=not use_40bit)
 
     # finalize
     write(writer, output)
@@ -436,20 +408,20 @@ def decrypt(input_file, output, password):
     # configure and verify
     output = generate_output_name(input_file, output, 'decrypted')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    writer = PdfFileWriter()
+    writer = PdfWriter()
 
     # specific verification
-    if not reader.isEncrypted:
+    if not reader.is_encrypted:
         reader.stream.close()
         raise click.BadArgumentUsage(message='File is not encrypted.')
 
     # build output
     success = reader.decrypt(password) > 0
     if success:
-        writer.appendPagesFromReader(reader)
+        writer.append_pages_from_reader(reader)
     else:
         reader.stream.close()
         raise click.BadParameter(message='Wrong password.')
@@ -475,10 +447,10 @@ def remove(input_file, output, images, links, text):
     # configure and verify
     output = generate_output_name(input_file, output, 'removed')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    writer = PdfFileWriter()
+    writer = PdfWriter()
 
     # specific verification
     if not any([images, links, text]):
@@ -486,13 +458,13 @@ def remove(input_file, output, images, links, text):
         raise click.BadParameter(message='No objects to remove specified.')
 
     # build output
-    writer.appendPagesFromReader(reader)
+    writer.append_pages_from_reader(reader)
     if images:
-        writer.removeImages()
+        writer.remove_images()
     if links:
-        writer.removeLinks()
+        writer.remove_links()
     if text:
-        writer.removeText()
+        writer.remove_text()
 
     # finalize
     write(writer, output)
@@ -511,12 +483,12 @@ def info(input_file):
     # configure and verify
     generate_output_name(input_file, None, 'info')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
 
     # build output
-    for k, v in reader.documentInfo.items():
+    for k, v in reader.metadata.items():
         click.echo(f'{k}: {v}')
 
     # finalize
@@ -536,14 +508,14 @@ def reverse(input_file, output):
     # configure and verify
     output = generate_output_name(input_file, output, 'reversed')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    writer = PdfFileWriter()
+    writer = PdfWriter()
 
     # build output
-    for i in reversed(list(range(reader.getNumPages()))):
-        writer.addPage(reader.getPage(i))
+    for i in reversed(list(range(len(reader.pages)))):
+        writer.add_page(reader.pages[i])
 
     # finalize
     write(writer, output)
@@ -555,7 +527,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.
 
@@ -565,15 +537,16 @@ def rotate(input_file, output, select_list, select_range, select_index, all, ang
     # configure and verify
     output = generate_output_name(input_file, output, 'rotated')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    writer = PdfFileWriter()
+    writer = PdfWriter()
 
     # if 'all' flag is set rotate every page
-    selection = list(range(reader.numPages)) if all else list(
-        generate_selection([select_list, select_range, select_index], validate_index(reader.numPages))
-    )
+    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:
@@ -581,9 +554,9 @@ def rotate(input_file, output, select_list, select_range, select_index, all, ang
         raise click.BadParameter('Rotation angle must be increment of 90.')
 
     # build output
-    writer.appendPagesFromReader(reader)
+    writer.append_pages_from_reader(reader)
     for i in selection:
-        writer.getPage(i).rotateClockwise(angle)
+        writer.pages[i].rotate(angle)
 
     # finalize
     write(writer, output)
@@ -599,7 +572,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.
@@ -610,23 +583,24 @@ def scale(input_file, output, select_list, select_range, select_index, all, scal
     # configure and verify
     output = generate_output_name(input_file, output, 'scaled')
     try:
-        reader = PdfFileReader(open(input_file, 'rb'))
+        reader = PdfReader(open(input_file, 'rb'))
     except PdfReadError:
         raise click.BadParameter('File cannot be read.')
-    writer = PdfFileWriter()
+    writer = PdfWriter()
 
     # if 'all' flag is set rotate every page
-    selection = list(range(reader.numPages)) if all else list(
-        generate_selection([select_list, select_range, select_index], validate_index(reader.numPages))
-    )
+    if all:
+        selection = set(range(len(reader.pages)))
+    else:
+        selection = validate_selection(select_pages, validate_index(len(reader.pages)))
 
     # build output
-    writer.appendPagesFromReader(reader)
+    writer.append_pages_from_reader(reader)
     for i in selection:
         if scale_to:
-            writer.getPage(i).scaleTo(horizontal, vertical)
+            writer.pages[i].scale_to(horizontal, vertical)
         else:
-            writer.getPage(i).scale(horizontal, vertical)
+            writer.pages[i].scale(horizontal, vertical)
 
     # finalize
     write(writer, output)


Test patch:
diff --git a/tests/test_pypdfcli.py b/tests/test_pypdf-cli.py
similarity index 55%
rename from tests/test_pypdfcli.py
rename to tests/test_pypdf-cli.py
index b0fd115..bec6c96 100644
--- a/tests/test_pypdfcli.py
+++ b/tests/test_pypdf-cli.py
@@ -3,7 +3,7 @@
 import pytest
 from click.testing import CliRunner
 
-from pypdfcli import *
+from pypdf_cli import *
 
 TEST_DIR = dirname(abspath(__file__))
 TEST_FILE = join(TEST_DIR, 'file.pdf')
@@ -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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res_reader.numPages):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 2}')
+    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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res_reader.numPages):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 5}')
+    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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res_reader.numPages):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 1}')
+    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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res_reader.numPages):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 5}')
+    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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    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.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(4, 6):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 7}')
+        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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    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.getPage(i).extractText().startswith(f'page{i + 2}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 2}')
     for i in range(3, 5):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 8}')
+        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,71 +222,75 @@ def test_extract():
     # result.exit_code = 0 -> okay
     # result.exit_code = 2 -> error raised
 
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-i', '1'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert res_reader.numPages == 1
-    assert res_reader.getPage(0).extractText().startswith('page1')
-
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-l', '1,2,3,4'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert res_reader.numPages == 4
-    for i in range(res_reader.numPages):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 1}')
-
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '10-12'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert res_reader.numPages == 3
-    for i in range(res_reader.numPages):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 10}')
-
-    RUNNER.invoke(extract, [TEST_FILE, '-o', TEST_OUT, '-r', '10-12', '-l', '1,2,3,4'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert res_reader.numPages == 7
+    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, '-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, '-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, '-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):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(4, 7):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 6}')
+        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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert res_reader.numPages == 7
-    assert res_reader.getPage(0).extractText().startswith('page1')
+    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.getPage(i).extractText().startswith(f'page{i + 4}')
+        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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    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.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(12, 24):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 11}')
+        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'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    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.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(12, 24):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 11}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
 
-    RUNNER.invoke(insert, [TEST_FILE, TEST_FILE, '-o', TEST_OUT, '-i', '6'])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    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.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(5, 17):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 4}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 4}')
     for i in range(17, 24):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 11}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
 
 
 def test_merge():
@@ -293,67 +298,72 @@ def test_merge():
     # result.exit_code = 2 -> error raised
 
     RUNNER.invoke(merge, [TEST_FILE, TEST_FILE, '-o', TEST_OUT])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(0, 12):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(12, 24):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 11}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
 
     RUNNER.invoke(merge, [TEST_FILE, TEST_FILE, TEST_FILE, '-o', TEST_OUT])
-    res_reader = PdfFileReader(open(TEST_OUT, 'rb'))
+    res_reader = PdfReader(open(TEST_OUT, 'rb'))
     for i in range(0, 12):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i + 1}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i + 1}')
     for i in range(12, 24):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 11}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 11}')
     for i in range(24, 36):
-        assert res_reader.getPage(i).extractText().startswith(f'page{i - 23}')
+        assert res_reader.pages[i].extract_text().startswith(f'page{i - 23}')
 
     result = RUNNER.invoke(merge, [TEST_FILE, '-o', TEST_OUT])
     assert result.exit_code == 2
 
+    # I'm sorry but testing merge without input files / merge --all for merging 
+    # all files in a current directory is a nightmare because testing with
+    # click doesn't let me implement a proper teardown to delete generated files
+    # making test cases for merge all manual anyway. Too much of a headache.
+
 
 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 = PdfFileReader(open(test_out_base + '_1.pdf', 'rb'))
+    res1 = PdfReader(open(test_out_base + '_1.pdf', 'rb'))
     for i in range(0, 3):
-        assert res1.getPage(i).extractText().startswith(f'page{i + 1}')
-    res2 = PdfFileReader(open(test_out_base + '_2.pdf', 'rb'))
+        assert res1.pages[i].extract_text().startswith(f'page{i + 1}')
+    res2 = PdfReader(open(test_out_base + '_2.pdf', 'rb'))
     for i in range(0, 9):
-        assert res2.getPage(i).extractText().startswith(f'page{i + 4}')
+        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 = PdfFileReader(open(test_out_base + '_1.pdf', 'rb'))
+    res1 = PdfReader(open(test_out_base + '_1.pdf', 'rb'))
     for i in range(0, 3):
-        assert res1.getPage(i).extractText().startswith(f'page{i + 1}')
-    res2 = PdfFileReader(open(test_out_base + '_2.pdf', 'rb'))
+        assert res1.pages[i].extract_text().startswith(f'page{i + 1}')
+    res2 = PdfReader(open(test_out_base + '_2.pdf', 'rb'))
     for i in range(0, 6):
-        assert res2.getPage(i).extractText().startswith(f'page{i + 4}')
-    res3 = PdfFileReader(open(test_out_base + '_3.pdf', 'rb'))
+        assert res2.pages[i].extract_text().startswith(f'page{i + 4}')
+    res3 = PdfReader(open(test_out_base + '_3.pdf', 'rb'))
     for i in range(0, 3):
-        assert res3.getPage(i).extractText().startswith(f'page{i + 10}')
+        assert res3.pages[i].extract_text().startswith(f'page{i + 10}')
 
     RUNNER.invoke(split, [TEST_FILE, '-o', TEST_OUT, '-a'])
     test_out_base = TEST_OUT[:-4]
     for i in range(1, 13):
-        res = PdfFileReader(open(test_out_base + f'_{buffer_number(2, i)}.pdf', 'rb'))
-        assert res.getPage(0).extractText().startswith(f'page{i}')
+        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
 
 
 def test_encrypt():
     RUNNER.invoke(encrypt, [TEST_FILE, '-o', TEST_OUT, '--user-password', '1234', '--owner-password', 'abcd'])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert res.isEncrypted
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    assert res.is_encrypted
     with pytest.raises(PdfReadError):
-        res.getPage(0)
+        res.pages[0]
     assert res.decrypt('wrong pw') == 0
     assert res.decrypt('1234') == 1
     assert res.decrypt('abcd') == 2
-    assert res.numPages == 12
+    assert len(res.pages) == 12
 
 
 def test_decrypt():
@@ -361,12 +371,12 @@ def test_decrypt():
     RUNNER.invoke(encrypt, [TEST_FILE, '-o', test_out_encrypted, '--user-password', '1234', '--owner-password', 'abcd'])
 
     RUNNER.invoke(decrypt, [test_out_encrypted, '-o', TEST_OUT, '--password', '1234'])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert not res.isEncrypted
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    assert not res.is_encrypted
 
     RUNNER.invoke(decrypt, [test_out_encrypted, '-o', TEST_OUT, '--password', 'abcd'])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    assert not res.isEncrypted
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    assert not res.is_encrypted
 
     result = RUNNER.invoke(decrypt, [test_out_encrypted, '-o', TEST_OUT, '--password', 'wrong pw'])
     assert result.exit_code == 2
@@ -380,12 +390,12 @@ def test_remove():
     assert result.exit_code == 2
 
     RUNNER.invoke(remove, [TEST_FILE, '-o', TEST_OUT, '--text'])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res.numPages):
-        assert res.getPage(i).extractText().strip() == ''
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    for i in range(len(res.pages)):
+        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():
@@ -398,37 +408,37 @@ def test_info():
 
 def test_reverse():
     RUNNER.invoke(reverse, [TEST_FILE, '-o', TEST_OUT])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res.numPages):
-        assert res.getPage(i).extractText().startswith(f'page{12 - i}')
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    for i in range(len(res.pages)):
+        assert res.pages[i].extract_text().startswith(f'page{12 - i}')
 
 
 def test_rotate():
     RUNNER.invoke(rotate, [TEST_FILE, '-o', TEST_OUT, '-a', '--angle', 90])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res.numPages):
-        page = res.getPage(i)
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    for i in range(len(res.pages)):
+        page = res.pages[i]
         rotate_obj = page.get("/Rotate", 0)
         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, '-a', '--angle', -90])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
-    for i in range(res.numPages):
-        page = res.getPage(i)
+    res = PdfReader(open(TEST_OUT, 'rb'))
+    for i in range(len(res.pages)):
+        page = res.pages[i]
         rotate_obj = page.get("/Rotate", 0)
         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])
-    res = PdfFileReader(open(TEST_OUT, 'rb'))
+    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.getPage(i)
+        page = res.pages[i]
         rotate_obj = page.get("/Rotate", 0)
         current_angle = rotate_obj if isinstance(rotate_obj, int) else rotate_obj.getObject()
         assert current_angle == 90
     for i in range(6, 12):
-        page = res.getPage(i)
+        page = res.pages[i]
         rotate_obj = page.get("/Rotate", 0)
         current_angle = rotate_obj if isinstance(rotate_obj, int) else rotate_obj.getObject()
         assert current_angle == 0
@@ -441,7 +451,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 +459,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