diff -pruN 4.0-4/.github/workflows/pypi-release.yml 5.0-1/.github/workflows/pypi-release.yml
--- 4.0-4/.github/workflows/pypi-release.yml	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/.github/workflows/pypi-release.yml	2025-04-03 10:31:43.000000000 +0000
@@ -1,26 +1,82 @@
-name: Release library as a PyPI wheel and sdist on tag
+name: Create library release archives, create a GH release and publish PyPI wheel and sdist on tag in main branch
+
+
+# This is executed automatically on a tag in the main branch
+
+# Summary of the steps:
+# - build wheels and sdist
+# - upload wheels and sdist to PyPI
+# - create gh-release and upload wheels and dists there
 
 on:
-  release:
-    types: [created]
+  workflow_dispatch:
+  push:
+    tags:
+      - "v*.*.*"
 
 jobs:
-  build-and-publish-to-pypi:
+  build-pypi-distribs:
     name: Build and publish library to PyPI
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-24.04
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install pypa/build and twine
+        run: python -m pip install --user --upgrade build twine packaging pip setuptools
+
+      - name: Build a binary wheel and a source tarball
+        run: python -m build --sdist --wheel --outdir dist/
+
+      - name: Validate wheel and sdis for Pypi
+        run: python -m twine check dist/*
+
+      - name: Upload built archives
+        uses: actions/upload-artifact@v4
+        with:
+          name: pypi_archives
+          path: dist/*
+
+
+  create-gh-release:
+    name: Create GH release
+    needs:
+      - build-pypi-distribs
+    runs-on: ubuntu-24.04
+
     steps:
-     - uses: actions/checkout@master
-     - name: Set up Python
-       uses: actions/setup-python@v1
-       with:
-         python-version: 3.9
-     - name: Install pypa/build
-       run: python -m pip install build --user
-     - name: Build a binary wheel and a source tarball
-       run: python -m build --sdist --wheel --outdir dist/
-        .
-     - name: Publish distribution to PyPI
-       if: startsWith(github.ref, 'refs/tags')
-       uses: pypa/gh-action-pypi-publish@master
-       with:
-         password: ${{ secrets.PYPI_API_TOKEN }}
+      - name: Download built archives
+        uses: actions/download-artifact@v4
+        with:
+          name: pypi_archives
+          path: dist
+
+      - name: Create GH release
+        uses: softprops/action-gh-release@v2
+        with:
+          draft: true
+          files: dist/*
+
+
+  create-pypi-release:
+    name: Create PyPI release
+    needs:
+      - create-gh-release
+    runs-on: ubuntu-24.04
+
+    steps:
+      - name: Download built archives
+        uses: actions/download-artifact@v4
+        with:
+          name: pypi_archives
+          path: dist
+
+      - name: Publish to PyPI
+        if: startsWith(github.ref, 'refs/tags')
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff -pruN 4.0-4/.github/workflows/test-and-build.yml 5.0-1/.github/workflows/test-and-build.yml
--- 4.0-4/.github/workflows/test-and-build.yml	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/.github/workflows/test-and-build.yml	2025-04-03 10:31:43.000000000 +0000
@@ -29,24 +29,24 @@ on: [push, pull_request, workflow_dispat
 jobs:
     build:
       name: Build source distribution
-      runs-on: ubuntu-20.04
+      runs-on: ubuntu-24.04
 
       steps:
-          - uses: actions/checkout@v2
+          - uses: actions/checkout@v4
 
           - name: Checkout and install reqs
-            run: |
-                pip install --upgrade --user build twine pip setuptools
+            run: python -m pip install --user --upgrade build twine packaging pip setuptools
 
           - name: Build sdist
             run: |
-                python setup.py sdist bdist_wheel
-                twine check dist/*
+                python -m build --sdist --wheel
+                python -m twine check dist/*
 
-          - name: Collect built sdist
-            uses: actions/upload-artifact@v2
+          - name: Collect built sdist and wheel
+            uses: actions/upload-artifact@v4
             with:
-                path: dist/*.tar.gz
+                name: boolean-build
+                path: dist/*
 
     test_on_many_oses:
         name: Run tests ${{ matrix.python }} on ${{ matrix.os }}
@@ -58,19 +58,40 @@ jobs:
         strategy:
             fail-fast: false
             matrix:
-                os: [ubuntu-20.04, macos-11, windows-2022]
-                python: ["3.6", "3.7", "3.8", "3.9", "3.10"]
+                os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14, windows-2019, windows-2022, windows-2025]
+                python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"]
 
         steps:
             - name: Set up Python
-              uses: actions/setup-python@v2
+              uses: actions/setup-python@v5
               with:
                 python-version: "${{ matrix.python }}"
 
-            - uses: actions/checkout@v2
+            - uses: actions/checkout@v4
 
             - name: Install
               run: pip install -e . -r requirements-dev.txt
 
             - name: Run tests
               run: pytest -vvs boolean
+
+    docs:
+      name: Generate docs
+      runs-on: ubuntu-24.04
+
+      steps:
+          - uses: actions/checkout@v4
+
+          - name: Checkout and install reqs
+            run: |
+                pip install --upgrade --user -e .[docs]
+
+          - name: Gen docs
+            run: |
+                make -C docs html
+
+          - name: Collect docs
+            uses: actions/upload-artifact@v4
+            with:
+                name: boolean-documentation
+                path: docs/build/
diff -pruN 4.0-4/.gitignore 5.0-1/.gitignore
--- 4.0-4/.gitignore	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/.gitignore	2025-04-03 10:31:43.000000000 +0000
@@ -10,3 +10,4 @@
 /.idea/
 .python-version
 /venv/
+/docs/
diff -pruN 4.0-4/CHANGELOG.rst 5.0-1/CHANGELOG.rst
--- 4.0-4/CHANGELOG.rst	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/CHANGELOG.rst	2025-04-03 10:31:43.000000000 +0000
@@ -6,6 +6,17 @@ Changelog
 next
 ----
 
+5.0 (2025-04-03)
+----------------
+
+* API changes
+
+ * Drop support for Python versions older than 3.9.
+ * Add support by testing on Python 3.11 to 3.14
+ * Fix absorption issues https://github.com/bastikr/boolean.py/issues/111 and
+   https://github.com/bastikr/boolean.py/issues/112
+
+
 
 4.0 (2022-05-05)
 ----------------
diff -pruN 4.0-4/boolean/__init__.py 5.0-1/boolean/__init__.py
--- 4.0-4/boolean/__init__.py	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/boolean/__init__.py	2025-04-03 10:31:43.000000000 +0000
@@ -7,6 +7,7 @@ look either into the docs directory or v
 https://booleanpy.readthedocs.org/en/latest/.
 
 Copyright (c) Sebastian Kraemer, basti.kr@gmail.com and others
+
 SPDX-License-Identifier: BSD-2-Clause
 """
 
diff -pruN 4.0-4/boolean/boolean.py 5.0-1/boolean/boolean.py
--- 4.0-4/boolean/boolean.py	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/boolean/boolean.py	2025-04-03 10:31:43.000000000 +0000
@@ -17,6 +17,7 @@ For extensive documentation look either
 online, at https://booleanpy.readthedocs.org/en/latest/.
 
 Copyright (c) Sebastian Kraemer, basti.kr@gmail.com and others
+
 SPDX-License-Identifier: BSD-2-Clause
 """
 
@@ -99,6 +100,7 @@ class ParseError(Exception):
 class BooleanAlgebra(object):
     """
     An algebra is defined by:
+
     - the types of its operations and Symbol.
     - the tokenizer used when parsing expressions from strings.
 
@@ -447,6 +449,7 @@ class BooleanAlgebra(object):
         unicode string.
 
         This 3-tuple contains (token, token string, position):
+
         - token: either a Symbol instance or one of TOKEN_* token types.
         - token string: the original token unicode string.
         - position: some simple object describing the starting position of the
@@ -464,27 +467,32 @@ class BooleanAlgebra(object):
         tests for other examples of alternative tokenizers.
 
         This tokenizer has these characteristics:
+
         - The `expr` string can span multiple lines,
         - Whitespace is not significant.
         - The returned position is the starting character offset of a token.
-
         - A TOKEN_SYMBOL is returned for valid identifiers which is a string
-        without spaces. These are valid identifiers:
-            - Python identifiers.
-            - a string even if starting with digits
-            - digits (except for 0 and 1).
-            - dotted names : foo.bar consist of one token.
-            - names with colons: foo:bar consist of one token.
-            These are not identifiers:
-            - quoted strings.
-            - any punctuation which is not an operation
+          without spaces.
+
+            - These are valid identifiers:
+                - Python identifiers.
+                - a string even if starting with digits
+                - digits (except for 0 and 1).
+                - dotted names : foo.bar consist of one token.
+                - names with colons: foo:bar consist of one token.
+            
+            - These are not identifiers:
+                - quoted strings.
+                - any punctuation which is not an operation
 
         - Recognized operators are (in any upper/lower case combinations):
+
             - for and:  '*', '&', 'and'
             - for or: '+', '|', 'or'
             - for not: '~', '!', 'not'
 
         - Recognized special symbols are (in any upper/lower case combinations):
+
             - True symbols: 1 and True
             - False symbols: 0, False and None
         """
@@ -572,7 +580,8 @@ class BooleanAlgebra(object):
         given AND or OR operation.
 
         The new expression arguments will satisfy these conditions:
-        - operation(*args) == expr (here mathematical equality is meant)
+    
+        - ``operation(*args) == expr`` (here mathematical equality is meant)
         - the operation does not occur in any of its arg.
         - NOT is only appearing in literals (aka. Negation normal form).
 
@@ -857,7 +866,12 @@ class Expression(object):
     def __gt__(self, other):
         lt = other.__lt__(self)
         if lt is NotImplemented:
-            return not self.__lt__(other)
+            self_lt = self.__lt__(other)
+            if self_lt is NotImplemented:
+                # `return not NotImplemented`` no longer works in Python 3.14
+                return False
+            else:
+                return not self_lt
         return lt
 
     def __and__(self, other):
@@ -1070,25 +1084,25 @@ class Function(Expression):
 
         If debug is True, also prints debug information for each expression arg.
 
-        For example::
+        For example:
 
-            >>> print(BooleanAlgebra().parse(
-            ...    u'not a and not b and not (a and ba and c) and c or c').pretty())
-            OR(
+        >>> print(BooleanAlgebra().parse(
+        ...    u'not a and not b and not (a and ba and c) and c or c').pretty())
+        OR(
+          AND(
+            NOT(Symbol('a')),
+            NOT(Symbol('b')),
+            NOT(
               AND(
-                NOT(Symbol('a')),
-                NOT(Symbol('b')),
-                NOT(
-                  AND(
-                    Symbol('a'),
-                    Symbol('ba'),
-                    Symbol('c')
-                  )
-                ),
+                Symbol('a'),
+                Symbol('ba'),
                 Symbol('c')
-              ),
-              Symbol('c')
-            )
+              )
+            ),
+            Symbol('c')
+          ),
+          Symbol('c')
+        )
         """
         debug_details = ""
         if debug:
@@ -1126,7 +1140,7 @@ class NOT(Function):
 
     You can subclass to define alternative string representation.
 
-    For example::
+    For example:
 
     >>> class NOT2(NOT):
     ...     def __init__(self, *args):
@@ -1226,7 +1240,7 @@ class DualBase(Function):
     Base class for AND and OR function.
 
     This class uses the duality principle to combine similar methods of AND
-    and OR. Both operations take 2 or more arguments and can be created using
+    and OR. Both operations take two or more arguments and can be created using
     "|" for OR and "&" for AND.
     """
 
@@ -1264,14 +1278,44 @@ class DualBase(Function):
 
         For simplification of AND and OR fthe ollowing rules are used
         recursively bottom up:
-         - Associativity (output does not contain same operations nested)
-         - Annihilation
-         - Idempotence
-         - Identity
-         - Complementation
-         - Elimination
-         - Absorption
-         - Commutativity (output is always sorted)
+
+        - Associativity (output does not contain same operations nested)::
+
+            (A & B) & C = A & (B & C) = A & B & C
+            (A | B) | C = A | (B | C) = A | B | C
+         
+         
+        - Annihilation::
+
+            A & 0 = 0, A | 1 = 1
+
+        - Idempotence (e.g. removing duplicates)::
+
+            A & A = A, A | A = A
+
+        - Identity::
+
+            A & 1 = A, A | 0 = A
+
+        - Complementation::
+
+            A & ~A = 0, A | ~A = 1
+
+        - Elimination::
+
+            (A & B) | (A & ~B) = A, (A | B) & (A | ~B) = A
+
+        - Absorption::
+
+            A & (A | B) = A, A | (A & B) = A
+
+        - Negative absorption::
+
+            A & (~A | B) = A & B, A | (~A & B) = A | B
+
+        - Commutativity (output is always sorted)::
+
+            A & B = B & A, A | B = B | A
 
         Other boolean objects are also in their canonical form.
         """
@@ -1389,7 +1433,9 @@ class DualBase(Function):
         Return a new expression where nested terms of this expression are
         flattened as far as possible.
 
-        E.g. A & (B & C) becomes A & B & C.
+        E.g.::
+
+            A & (B & C) becomes A & B & C.
         """
         args = list(self.args)
         i = 0
@@ -1409,8 +1455,13 @@ class DualBase(Function):
 
         See https://en.wikipedia.org/wiki/Absorption_law
 
-        Absorption: A & (A | B) = A, A | (A & B) = A
-        Negative absorption: A & (~A | B) = A & B, A | (~A & B) = A | B
+        Absorption::
+
+            A & (A | B) = A, A | (A & B) = A
+
+        Negative absorption::
+
+            A & (~A | B) = A & B, A | (~A & B) = A | B
         """
         args = list(args)
         if not args:
@@ -1445,7 +1496,12 @@ class DualBase(Function):
                             i -= 1
                         continue
                     else:
-                        args[j] = b
+                        if b in args:
+                            del args[j]
+                            if j < i:
+                                i -= 1
+                        else:
+                            args[j] = b
                         j += 1
                         continue
 
@@ -1497,7 +1553,8 @@ class DualBase(Function):
         """
         Return a term where the leading AND or OR terms are switched.
 
-        This is done by applying the distributive laws:
+        This is done by applying the distributive laws::
+
             A & (B|C) = (A&B) | (A&C)
             A | (B&C) = (A|B) & (A|C)
         """
@@ -1551,12 +1608,15 @@ class DualBase(Function):
 
 class AND(DualBase):
     """
-    Boolean AND operation, taking 2 or more arguments.
+    Boolean AND operation, taking two or more arguments.
 
     It can also be created by using "&" between two boolean expressions.
 
-    You can subclass to define alternative string representation.
-    For example::
+    You can subclass to define alternative string representation by overriding
+    self.operator.
+    
+    For example:
+
     >>> class AND2(AND):
     ...     def __init__(self, *args):
     ...         super(AND2, self).__init__(*args)
@@ -1576,12 +1636,14 @@ class AND(DualBase):
 
 class OR(DualBase):
     """
-    Boolean OR operation, taking 2 or more arguments
+    Boolean OR operation, taking two or more arguments
 
     It can also be created by using "|" between two boolean expressions.
 
-    You can subclass to define alternative string representation.
-    For example::
+    You can subclass to define alternative string representation by overriding
+    self.operator.
+
+    For example:
 
     >>> class OR2(OR):
     ...     def __init__(self, *args):
diff -pruN 4.0-4/boolean/test_boolean.py 5.0-1/boolean/test_boolean.py
--- 4.0-4/boolean/test_boolean.py	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/boolean/test_boolean.py	2025-04-03 10:31:43.000000000 +0000
@@ -760,6 +760,28 @@ class DualBaseTestCase(unittest.TestCase
         )
         assert result.pretty() == expected.pretty()
 
+    def test_absorption_invariant_to_order(self):
+        algebra = BooleanAlgebra()
+
+        a, b = algebra.symbols("a", "b")
+
+        e = (~a | ~b) & b & ~a
+        args = [
+            ~a | ~b,
+            ~a,
+            b,
+        ]
+
+        result_original = e.absorb(args)
+
+        args[1], args[2] = args[2], args[1]
+        result_swapped = e.absorb(args)
+
+        assert len(result_original) == 2
+        assert len(result_swapped) == 2
+        assert result_original[0] == result_swapped[1]
+        assert result_original[1] == result_swapped[0]
+
     @expectedFailure
     def test_parse_complex_expression_should_create_same_expression_as_python(self):
         algebra = BooleanAlgebra()
diff -pruN 4.0-4/debian/changelog 5.0-1/debian/changelog
--- 4.0-4/debian/changelog	2022-10-14 10:47:23.000000000 +0000
+++ 5.0-1/debian/changelog	2025-12-17 01:15:59.000000000 +0000
@@ -1,3 +1,13 @@
+python-boolean.py (5.0-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release:
+    - Work around incompatibility with Python 3.14 (closes: #1123202).
+  * Use pybuild-plugin-pyproject.
+  * Drop "Rules-Requires-Root: no", default as of dpkg-dev 1.22.13.
+
+ -- Colin Watson <cjwatson@debian.org>  Wed, 17 Dec 2025 01:15:59 +0000
+
 python-boolean.py (4.0-4) unstable; urgency=medium
 
   [ Debian Janitor ]
diff -pruN 4.0-4/debian/control 5.0-1/debian/control
--- 4.0-4/debian/control	2022-10-14 10:47:23.000000000 +0000
+++ 5.0-1/debian/control	2025-12-17 01:15:59.000000000 +0000
@@ -7,11 +7,12 @@ Build-Depends:
  debhelper-compat (= 13),
  dh-sequence-python3,
  dh-sequence-sphinxdoc,
+ pybuild-plugin-pyproject,
  python3-all,
  python3-setuptools,
  python3-pytest,
  python3-sphinx,
-Rules-Requires-Root: no
+ python3-sphinxcontrib.apidoc,
 Standards-Version: 4.6.1
 Homepage: https://github.com/bastikr/boolean.py
 Vcs-Browser: https://salsa.debian.org/python-team/packages/python-boolean.py
diff -pruN 4.0-4/debian/patches/0001_doc-local-mathjax.patch 5.0-1/debian/patches/0001_doc-local-mathjax.patch
--- 4.0-4/debian/patches/0001_doc-local-mathjax.patch	2022-10-14 10:47:23.000000000 +0000
+++ 5.0-1/debian/patches/0001_doc-local-mathjax.patch	2025-12-17 01:15:59.000000000 +0000
@@ -3,7 +3,7 @@ Forwarded: not-needed
 Author: Stephan Lachnit <stephanlachnit@debian.org>
 --- a/docs/conf.py
 +++ b/docs/conf.py
-@@ -175,6 +175,9 @@
+@@ -191,6 +191,9 @@
  # Output file base name for HTML help builder.
  htmlhelp_basename = "booleanpydoc"
  
diff -pruN 4.0-4/docs/Makefile 5.0-1/docs/Makefile
--- 4.0-4/docs/Makefile	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/docs/Makefile	2025-04-03 10:31:43.000000000 +0000
@@ -1,88 +1,24 @@
-# Makefile for Sphinx documentation
+# Minimal makefile for Sphinx documentation
 #
 
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+#SOURCEDIR     = source
+BUILDDIR      = build
 
-.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+#clean:
+#	-rm -rf .build/&
 
+# Put it first so that "make" without argument is like "make help".
 help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html      to make standalone HTML files"
-	@echo "  dirhtml   to make HTML files named index.html in directories"
-	@echo "  pickle    to make pickle files"
-	@echo "  json      to make JSON files"
-	@echo "  htmlhelp  to make HTML files and a HTML help project"
-	@echo "  qthelp    to make HTML files and a qthelp project"
-	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  changes   to make an overview of all changed/added/deprecated items"
-	@echo "  linkcheck to check all external links for integrity"
-	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
-
-clean:
-	-rm -rf .build/&
-
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
-	@echo
-	@echo "Build finished. The HTML pages are in .build/html."
-
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) .build/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in .build/dirhtml."
-
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) .build/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in .build/htmlhelp."
-
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) .build/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in .build/qthelp, like this:"
-	@echo "# qcollectiongenerator .build/qthelp/boolean.py.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile .build/qthelp/boolean.py.qhc"
-
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in .build/latex."
-	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
-	      "run these through (pdf)latex."
-
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
-	@echo
-	@echo "The overview file is in .build/changes."
-
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in .build/linkcheck/output.txt."
-
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) .build/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in .build/doctest/output.txt."
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
diff -pruN 4.0-4/docs/api/boolean.rst 5.0-1/docs/api/boolean.rst
--- 4.0-4/docs/api/boolean.rst	1970-01-01 00:00:00.000000000 +0000
+++ 5.0-1/docs/api/boolean.rst	2025-04-03 10:31:43.000000000 +0000
@@ -0,0 +1,18 @@
+boolean package
+===============
+
+.. automodule:: boolean
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+boolean.boolean module
+----------------------
+
+.. automodule:: boolean.boolean
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff -pruN 4.0-4/docs/api/modules.rst 5.0-1/docs/api/modules.rst
--- 4.0-4/docs/api/modules.rst	1970-01-01 00:00:00.000000000 +0000
+++ 5.0-1/docs/api/modules.rst	2025-04-03 10:31:43.000000000 +0000
@@ -0,0 +1,7 @@
+boolean
+=======
+
+.. toctree::
+   :maxdepth: 4
+
+   boolean
diff -pruN 4.0-4/docs/conf.py 5.0-1/docs/conf.py
--- 4.0-4/docs/conf.py	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/docs/conf.py	2025-04-03 10:31:43.000000000 +0000
@@ -11,12 +11,16 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys, os
+# -- Path setup --------------------------------------------------------------
 
 # If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.append(os.path.abspath("../boolean/"))
+# add these directories to sys.path here.
+
+import pathlib
+import sys
+
+srcdir = pathlib.Path(__file__).resolve().parents[1]
+sys.path.insert(0, srcdir.as_posix())
 
 # -- General configuration -----------------------------------------------------
 
@@ -27,8 +31,20 @@ extensions = [
     "sphinx.ext.doctest",
     "sphinx.ext.mathjax",
     "sphinx.ext.inheritance_diagram",
+    "sphinx.ext.intersphinx",
+    "sphinxcontrib.apidoc",
 ]
 
+# Setting for sphinxcontrib.apidoc to automatically create API documentation.
+apidoc_module_dir = srcdir.joinpath('boolean').as_posix()
+apidoc_excluded_paths = [srcdir.joinpath('boolean').joinpath('test_boolean.py').as_posix()]
+apidoc_module_first = True
+
+# Reference to other Sphinx documentations
+intersphinx_mapping = {
+    "python": ("https://docs.python.org/3", None),
+}
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = [".templates"]
 
@@ -135,7 +151,7 @@ html_theme = "default"
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = [".static"]
+#html_static_path = [".static"]
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
diff -pruN 4.0-4/docs/index.rst 5.0-1/docs/index.rst
--- 4.0-4/docs/index.rst	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/docs/index.rst	2025-04-03 10:31:43.000000000 +0000
@@ -8,5 +8,6 @@ boolean.py documentation
    users_guide
    concepts
    development_guide
+   API<api/modules>
    acknowledgements
 
diff -pruN 4.0-4/setup.py 5.0-1/setup.py
--- 4.0-4/setup.py	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/setup.py	2025-04-03 10:31:43.000000000 +0000
@@ -1,8 +1,7 @@
 #!/usr/bin/env python
 
-from __future__ import absolute_import
-
-from setuptools import find_packages, setup
+from setuptools import find_packages
+from setuptools import setup
 
 long_desc = """
 
@@ -24,7 +23,7 @@ SPDX-License-Identifier: BSD-2-Clause
 
 setup(
     name="boolean.py",
-    version="4.0",
+    version="5.0",
     license="BSD-2-Clause",
     description="Define boolean algebras, create and parse boolean "
     "expressions and create custom boolean DSL.",
@@ -36,8 +35,6 @@ setup(
     packages=find_packages(),
     include_package_data=True,
     zip_safe=False,
-    test_loader="unittest:TestLoader",
-    test_suite="boolean.test_boolean",
     keywords="boolean expression, boolean algebra, logic, expression parser",
     classifiers=[
         "Development Status :: 5 - Production/Stable",
@@ -50,4 +47,29 @@ setup(
         "Topic :: Software Development :: Libraries",
         "Topic :: Utilities",
     ],
+    extras_require={
+        "testing":
+            [
+                "pytest >= 6, != 7.0.0",
+                "pytest-xdist >= 2",
+            ],
+        "dev":
+            [
+                "twine",
+                "build",
+            ],
+        "linting":
+            [
+                "black",
+                "isort",
+                "pycodestyle",
+            ],
+        "docs":
+            [
+                "Sphinx >= 3.3.1",
+                "sphinx-rtd-theme >= 0.5.0",
+                "doc8 >= 0.8.1",
+                "sphinxcontrib-apidoc >= 0.3.0",
+            ],
+    }
 )
diff -pruN 4.0-4/tox.ini 5.0-1/tox.ini
--- 4.0-4/tox.ini	2022-05-05 08:17:22.000000000 +0000
+++ 5.0-1/tox.ini	2025-04-03 10:31:43.000000000 +0000
@@ -1,4 +1,5 @@
 [tox]
-envlist=py36,py37,py38,py39,310
+envlist=py39,py310,py311,py312,py313,py314
 [testenv]
-commands=python setup.py test
\ No newline at end of file
+extras=testing
+commands=pytest -vvs boolean
\ No newline at end of file
