Repository: roskakori/pygount (language: python)

Issue:
Change linting to ruff
## Goal

- [ ] The pre-commit hooks use ruff for linting.

Estimate: 3h



Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index c923419..0000000
--- a/.flake8
+++ /dev/null
@@ -1,3 +0,0 @@
-[flake8]
-ignore = E203,W503
-max-line-length = 120
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5209192..a740a42 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,15 +1,12 @@
 exclude: "^.idea"
 
 repos:
-  - repo: https://github.com/pre-commit/mirrors-isort
-    rev: v5.10.1
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.5.1
     hooks:
-      - id: isort
-
-  - repo: https://github.com/ambv/black
-    rev: 24.4.2
-    hooks:
-      - id: black
+      - id: ruff
+        args: ["--fix"]
+      - id: ruff-format
 
   - repo: https://github.com/pre-commit/mirrors-prettier
     rev: v3.1.0
@@ -31,14 +28,8 @@ repos:
       - id: check-yaml
       - id: debug-statements
 
-  - repo: https://github.com/asottile/pyupgrade
-    rev: v3.15.2
-    hooks:
-      - id: pyupgrade
-        args: ["--py38-plus"]
-
-  - repo: https://github.com/PyCQA/flake8
-    rev: 7.0.0
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.6.0
     hooks:
-      - id: flake8
-        additional_dependencies: ["pep8-naming==0.13.3"]
+      - id: no-commit-to-branch
+        args: ["--branch", "master"]
diff --git a/docs/changes.rst b/docs/changes.rst
index f1a56ff..8123f11 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -5,6 +5,11 @@ Changes
 
 This chapter describes the changes coming with each new version of pygount.
 
+Version 1.8.1, 2024-07-06
+
+* Development: Change linter to ruff and in turn clean up code (issue
+  `#157 <https://github.com/roskakori/pygount/issues/157>`_).
+
 Version 1.8.0, 2024-05-13
 
 * Add all available counts and percentages to JSON format (issue
diff --git a/docs/conf.py b/docs/conf.py
index 2f68c82..c5de3ae 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,7 +18,7 @@
 # -- Project information -----------------------------------------------------
 
 project = "pygount"
-copyright = "2016-2024, Thomas Aglassinger"
+copyright = "2016-2024, Thomas Aglassinger"  # noqa: A001
 author = "Thomas Aglassinger"
 
 # The full version, including alpha/beta/rc tags
diff --git a/poetry.lock b/poetry.lock
index 30c5def..28faa28 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -30,28 +30,28 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
 
 [[package]]
 name = "backports-tarfile"
-version = "1.1.1"
+version = "1.2.0"
 description = "Backport of CPython tarfile module"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "backports.tarfile-1.1.1-py3-none-any.whl", hash = "sha256:73e0179647803d3726d82e76089d01d8549ceca9bace469953fcb4d97cf2d417"},
-    {file = "backports_tarfile-1.1.1.tar.gz", hash = "sha256:9c2ef9696cb73374f7164e17fc761389393ca76777036f5aad42e8b93fcd8009"},
+    {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"},
+    {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"},
 ]
 
 [package.extras]
 docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["jaraco.test", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"]
+testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"]
 
 [[package]]
 name = "certifi"
-version = "2024.2.2"
+version = "2024.7.4"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
-    {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
+    {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
+    {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
 ]
 
 [[package]]
@@ -266,63 +266,63 @@ files = [
 
 [[package]]
 name = "coverage"
-version = "7.4.4"
+version = "7.5.4"
 description = "Code coverage measurement for Python"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"},
-    {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"},
-    {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"},
-    {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"},
-    {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"},
-    {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"},
-    {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"},
-    {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"},
-    {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"},
-    {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"},
-    {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"},
-    {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"},
-    {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"},
-    {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"},
-    {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"},
-    {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"},
-    {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"},
-    {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"},
-    {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"},
-    {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"},
-    {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"},
-    {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"},
-    {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"},
-    {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"},
-    {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"},
-    {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"},
-    {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"},
-    {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"},
-    {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"},
-    {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"},
-    {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"},
-    {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"},
-    {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"},
-    {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"},
-    {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"},
-    {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"},
-    {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"},
-    {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"},
-    {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"},
-    {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"},
-    {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"},
-    {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"},
-    {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"},
-    {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"},
-    {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"},
-    {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"},
-    {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"},
-    {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"},
-    {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"},
-    {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"},
-    {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"},
-    {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"},
+    {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"},
+    {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"},
+    {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"},
+    {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"},
+    {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"},
+    {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"},
+    {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"},
+    {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"},
+    {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"},
+    {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"},
+    {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"},
+    {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"},
+    {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"},
+    {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"},
+    {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"},
+    {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"},
+    {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"},
+    {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"},
+    {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"},
+    {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"},
+    {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"},
+    {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"},
+    {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"},
+    {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"},
+    {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"},
+    {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"},
+    {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"},
+    {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"},
+    {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"},
+    {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"},
+    {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"},
+    {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"},
+    {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"},
+    {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"},
+    {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"},
+    {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"},
+    {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"},
+    {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"},
+    {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"},
+    {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"},
+    {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"},
+    {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"},
+    {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"},
+    {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"},
+    {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"},
+    {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"},
+    {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"},
+    {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"},
+    {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"},
+    {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"},
+    {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"},
+    {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"},
 ]
 
 [package.dependencies]
@@ -333,17 +333,17 @@ toml = ["tomli"]
 
 [[package]]
 name = "coveralls"
-version = "4.0.0"
+version = "4.0.1"
 description = "Show coverage stats online via coveralls.io"
 optional = false
 python-versions = "<3.13,>=3.8"
 files = [
-    {file = "coveralls-4.0.0-py3-none-any.whl", hash = "sha256:401715d244a27d5da03eb1ac614aa585cc7e4dd5b0d4c035113b6349da4e6161"},
-    {file = "coveralls-4.0.0.tar.gz", hash = "sha256:9486f353176d309066053d38edbade3aad6346c5eb8a5edde7090d3116219414"},
+    {file = "coveralls-4.0.1-py3-none-any.whl", hash = "sha256:7a6b1fa9848332c7b2221afb20f3df90272ac0167060f41b5fe90429b30b1809"},
+    {file = "coveralls-4.0.1.tar.gz", hash = "sha256:7b2a0a2bcef94f295e3cf28dcc55ca40b71c77d1c2446b538e85f0f7bc21aa69"},
 ]
 
 [package.dependencies]
-coverage = {version = ">=5.0,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.5", extras = ["toml"]}
+coverage = {version = ">=5.0,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<8.0", extras = ["toml"]}
 docopt = ">=0.6.1,<0.7.0"
 requests = ">=1.0.0,<3.0.0"
 
@@ -352,43 +352,43 @@ yaml = ["pyyaml (>=3.10,<7.0)"]
 
 [[package]]
 name = "cryptography"
-version = "42.0.7"
+version = "42.0.8"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"},
-    {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"},
-    {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"},
-    {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"},
-    {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"},
-    {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"},
-    {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"},
-    {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"},
-    {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"},
-    {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"},
-    {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"},
-    {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"},
-    {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"},
-    {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"},
-    {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"},
-    {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"},
-    {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"},
-    {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"},
-    {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"},
-    {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"},
-    {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"},
-    {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"},
-    {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"},
-    {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"},
-    {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"},
-    {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"},
-    {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"},
-    {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"},
-    {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"},
-    {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"},
-    {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"},
-    {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"},
+    {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"},
+    {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"},
+    {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"},
+    {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"},
+    {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"},
+    {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"},
+    {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"},
+    {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"},
+    {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"},
+    {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"},
+    {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"},
+    {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"},
+    {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"},
+    {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"},
+    {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"},
+    {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"},
+    {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"},
+    {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"},
+    {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"},
+    {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"},
+    {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"},
+    {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"},
+    {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"},
+    {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"},
+    {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"},
+    {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"},
+    {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"},
+    {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"},
+    {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"},
+    {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"},
+    {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"},
+    {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
 ]
 
 [package.dependencies]
@@ -452,18 +452,18 @@ test = ["pytest (>=6)"]
 
 [[package]]
 name = "filelock"
-version = "3.14.0"
+version = "3.15.4"
 description = "A platform independent file lock."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
-    {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"},
+    {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"},
+    {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"},
 ]
 
 [package.extras]
 docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
 typing = ["typing-extensions (>=4.8)"]
 
 [[package]]
@@ -536,22 +536,22 @@ files = [
 
 [[package]]
 name = "importlib-metadata"
-version = "7.1.0"
+version = "8.0.0"
 description = "Read metadata from Python packages"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
-    {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
+    {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
+    {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
 ]
 
 [package.dependencies]
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
 perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
+test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
 
 [[package]]
 name = "importlib-resources"
@@ -670,13 +670,13 @@ i18n = ["Babel (>=2.7)"]
 
 [[package]]
 name = "keyring"
-version = "25.2.0"
+version = "25.2.1"
 description = "Store and access your passwords safely."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "keyring-25.2.0-py3-none-any.whl", hash = "sha256:19f17d40335444aab84b19a0d16a77ec0758a9c384e3446ae2ed8bd6d53b67a5"},
-    {file = "keyring-25.2.0.tar.gz", hash = "sha256:7045f367268ce42dba44745050164b431e46f6e92f99ef2937dfadaef368d8cf"},
+    {file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"},
+    {file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"},
 ]
 
 [package.dependencies]
@@ -800,13 +800,13 @@ files = [
 
 [[package]]
 name = "more-itertools"
-version = "10.2.0"
+version = "10.3.0"
 description = "More routines for operating on iterables, beyond itertools"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"},
-    {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"},
+    {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"},
+    {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"},
 ]
 
 [[package]]
@@ -836,27 +836,24 @@ files = [
 
 [[package]]
 name = "nodeenv"
-version = "1.8.0"
+version = "1.9.1"
 description = "Node.js virtual environment builder"
 optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 files = [
-    {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
-    {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
+    {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
+    {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
 ]
 
-[package.dependencies]
-setuptools = "*"
-
 [[package]]
 name = "packaging"
-version = "24.0"
+version = "24.1"
 description = "Core utilities for Python packages"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
-    {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
+    {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+    {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
 ]
 
 [[package]]
@@ -875,13 +872,13 @@ testing = ["pytest", "pytest-cov", "wheel"]
 
 [[package]]
 name = "platformdirs"
-version = "4.2.1"
+version = "4.2.2"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
-    {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
+    {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
+    {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
 ]
 
 [package.extras]
@@ -924,12 +921,12 @@ virtualenv = ">=20.10.0"
 
 [[package]]
 name = "pur"
-version = "7.3.1"
+version = "7.3.2"
 description = "Update packages in a requirements.txt file to latest versions."
 optional = false
 python-versions = "*"
 files = [
-    {file = "pur-7.3.1.tar.gz", hash = "sha256:ac693e4c1b7c8fa376b198ce797488befa65f0965ce13b4a8659fd44113a24ba"},
+    {file = "pur-7.3.2.tar.gz", hash = "sha256:5f1643e3853b8c3b04850ddf60d52e1e121895399b69361efcf7c9b6ac1f5d9c"},
 ]
 
 [package.dependencies]
@@ -962,13 +959,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
 
 [[package]]
 name = "pytest"
-version = "8.2.0"
+version = "8.2.2"
 description = "pytest: simple powerful testing with Python"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"},
-    {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"},
+    {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
+    {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
 ]
 
 [package.dependencies]
@@ -1103,13 +1100,13 @@ md = ["cmarkgfm (>=0.8.0)"]
 
 [[package]]
 name = "requests"
-version = "2.32.0"
+version = "2.32.3"
 description = "Python HTTP for Humans."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"},
-    {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"},
+    {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
+    {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
 ]
 
 [package.dependencies]
@@ -1169,6 +1166,33 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9
 [package.extras]
 jupyter = ["ipywidgets (>=7.5.1,<9)"]
 
+[[package]]
+name = "ruff"
+version = "0.5.1"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"},
+    {file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"},
+    {file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"},
+    {file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"},
+    {file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"},
+    {file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"},
+    {file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"},
+    {file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"},
+    {file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"},
+    {file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"},
+    {file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"},
+    {file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
+]
+
 [[package]]
 name = "secretstorage"
 version = "3.3.3"
@@ -1184,22 +1208,6 @@ files = [
 cryptography = ">=2.0"
 jeepney = ">=0.6"
 
-[[package]]
-name = "setuptools"
-version = "69.5.1"
-description = "Easily download, build, install, upgrade, and uninstall Python packages"
-optional = false
-python-versions = ">=3.8"
-files = [
-    {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
-    {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
-
 [[package]]
 name = "smmap"
 version = "5.0.1"
@@ -1359,19 +1367,19 @@ files = [
 
 [[package]]
 name = "twine"
-version = "5.0.0"
+version = "5.1.1"
 description = "Collection of utilities for publishing packages on PyPI"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "twine-5.0.0-py3-none-any.whl", hash = "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0"},
-    {file = "twine-5.0.0.tar.gz", hash = "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4"},
+    {file = "twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997"},
+    {file = "twine-5.1.1.tar.gz", hash = "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db"},
 ]
 
 [package.dependencies]
 importlib-metadata = ">=3.6"
 keyring = ">=15.1"
-pkginfo = ">=1.8.1"
+pkginfo = ">=1.8.1,<1.11"
 readme-renderer = ">=35.0"
 requests = ">=2.20"
 requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0"
@@ -1381,13 +1389,13 @@ urllib3 = ">=1.26.0"
 
 [[package]]
 name = "typing-extensions"
-version = "4.11.0"
+version = "4.12.2"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
-    {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
+    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
 ]
 
 [[package]]
@@ -1409,13 +1417,13 @@ zstd = ["zstandard (>=0.18.0)"]
 
 [[package]]
 name = "virtualenv"
-version = "20.26.1"
+version = "20.26.3"
 description = "Virtual Python Environment builder"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"},
-    {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"},
+    {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
+    {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
 ]
 
 [package.dependencies]
@@ -1429,20 +1437,20 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
 
 [[package]]
 name = "zipp"
-version = "3.18.1"
+version = "3.19.2"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
-    {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
+    {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
+    {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
 ]
 
 [package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
 
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8, <3.13"
-content-hash = "507b4b3300e29ca13f769c3de252398c140a43f28cd855a1e30dbbd355c48d28"
+content-hash = "6355e18bc96daceb292ea64b915f8be72ad456d79289f62b9f77878c0aab49f6"
diff --git a/pygount/analysis.py b/pygount/analysis.py
index 677b915..5246e4f 100644
--- a/pygount/analysis.py
+++ b/pygount/analysis.py
@@ -24,7 +24,7 @@
 import pygount.common
 import pygount.lexers
 import pygount.xmldialect
-from pygount.common import deprecated
+from pygount.common import deprecated, mapped_repr
 from pygount.git_storage import GitStorage, git_remote_url_and_revision_if_any
 
 # Attempt to import chardet.
@@ -481,17 +481,24 @@ def is_countable(self) -> bool:
         return self.state in (SourceState.analyzed, SourceState.duplicate)
 
     def __repr__(self):
-        result = "{}(path={!r}, language={!r}, group={!r}, state={}".format(
-            self.__class__.__name__, self.path, self.language, self.group, self.state.name
-        )
+        name_to_value_map = {
+            "path": repr(self.path),
+            "language": repr(self.language),
+            "group": repr(self.group),
+            "state": self.state.name,
+        }
         if self.state == SourceState.analyzed:
-            result += ", code_count={}, documentation_count={}, empty_count={}, string_count={}".format(
-                self.code_count, self.documentation_count, self.empty_count, self.string_count
+            name_to_value_map.update(
+                {
+                    "code_count": self.code_count,
+                    "documentation_count": self.documentation_count,
+                    "empty_count": self.empty_count,
+                    "string_count": self.string_count,
+                }
             )
         if self.state_info is not None:
-            result += f", state_info={self.state_info!r}"
-        result += ")"
-        return result
+            name_to_value_map["state_info"] = repr(self.state_info)
+        return mapped_repr(self, name_to_value_map)
 
 
 class SourceScanner:
@@ -558,7 +565,7 @@ def name_regexps_to_skip(self, regexps_or_pattern_text):
         self._name_regexps_to_skip = pygount.common.regexes_from(regexps_or_pattern_text, self.name_regexps_to_skip)
 
     def _is_path_to_skip(self, name, is_folder) -> bool:
-        assert os.sep not in name, "name=%r" % name
+        assert os.sep not in name, f"name={name!r}"
         regexps_to_skip = self._folder_regexps_to_skip if is_folder else self._name_regexps_to_skip
         return any(path_name_to_skip_regex.match(name) is not None for path_name_to_skip_regex in regexps_to_skip)
 
@@ -604,8 +611,11 @@ def _paths_and_group_to_analyze(self, path_to_analyse_pattern, group=None) -> It
     def _source_paths_and_groups_to_analyze(self, source_patterns_to_analyze) -> List[Tuple[str, str]]:
         assert source_patterns_to_analyze is not None
         result = []
-        for source_pattern_to_analyze in source_patterns_to_analyze:
-            try:
+        # NOTE: We could avoid initializing `source_pattern_to_analyze` here by moving the `try` inside
+        #  the loop, but this would incor a performance overhead (ruff's PERF203).
+        source_pattern_to_analyze = None
+        try:
+            for source_pattern_to_analyze in source_patterns_to_analyze:
                 remote_url, revision = git_remote_url_and_revision_if_any(source_pattern_to_analyze)
                 if remote_url is not None:
                     git_storage = GitStorage(remote_url, revision)
@@ -615,8 +625,9 @@ def _source_paths_and_groups_to_analyze(self, source_patterns_to_analyze) -> Lis
                     result.extend(self._paths_and_group_to_analyze(git_storage.temp_folder))
                 else:
                     result.extend(self._paths_and_group_to_analyze(source_pattern_to_analyze))
-            except OSError as error:
-                raise OSError(f'cannot scan "{source_pattern_to_analyze}" for source files: {error}')
+        except OSError as error:
+            assert source_pattern_to_analyze is not None
+            raise OSError(f'cannot scan "{source_pattern_to_analyze}" for source files: {error}') from error
         result = sorted(set(result))
         return result
 
@@ -636,7 +647,7 @@ def source_paths(self) -> Iterator[str]:
 
 
 _LANGUAGE_TO_WHITE_WORDS_MAP = {"batchfile": {"@"}, "python": {"pass"}, "sql": {"begin", "end"}}
-for _language in _LANGUAGE_TO_WHITE_WORDS_MAP.keys():
+for _language in _LANGUAGE_TO_WHITE_WORDS_MAP:
     assert _language.islower()
 
 
@@ -686,32 +697,35 @@ def white_code_words(language_id: str) -> Set[str]:
     return _LANGUAGE_TO_WHITE_WORDS_MAP.get(language_id, set())
 
 
-def _delined_tokens(tokens: Sequence[Tuple[TokenType, str]]) -> Iterator[TokenType]:
+def _delined_tokens(tokens: Iterator[Tuple[TokenType, str]]) -> Iterator[TokenType]:
     for token_type, token_text in tokens:
-        newline_index = token_text.find("\n")
+        remaining_token_text = token_text
+        newline_index = remaining_token_text.find("\n")
         while newline_index != -1:
-            yield token_type, token_text[: newline_index + 1]
-            token_text = token_text[newline_index + 1 :]
-            newline_index = token_text.find("\n")
-        if token_text != "":
-            yield token_type, token_text
+            yield token_type, remaining_token_text[: newline_index + 1]
+            remaining_token_text = remaining_token_text[newline_index + 1 :]
+            newline_index = remaining_token_text.find("\n")
+        if remaining_token_text != "":
+            yield token_type, remaining_token_text
 
 
-def _pythonized_comments(tokens: Sequence[Tuple[TokenType, str]]) -> Iterator[TokenType]:
+def _pythonized_comments(tokens: Iterator[Tuple[TokenType, str]]) -> Iterator[TokenType]:
     """
     Similar to tokens but converts strings after a colon (`:`) to comments.
     """
     is_after_colon = True
-    for token_type, token_text in tokens:
+    for token_type, result_token_text in tokens:
         if is_after_colon and (token_type in pygments.token.String):
-            token_type = pygments.token.Comment
-        elif token_text == ":":
-            is_after_colon = True
-        elif token_type not in pygments.token.Comment:
-            is_whitespace = len(token_text.rstrip(" \f\n\r\t")) == 0
-            if not is_whitespace:
-                is_after_colon = False
-        yield token_type, token_text
+            result_token_type = pygments.token.Comment
+        else:
+            result_token_type = token_type
+            if result_token_text == ":":
+                is_after_colon = True
+            elif token_type not in pygments.token.Comment:
+                is_whitespace = len(result_token_text.rstrip(" \f\n\r\t")) == 0
+                if not is_whitespace:
+                    is_after_colon = False
+        yield result_token_type, result_token_text
 
 
 def _line_parts(lexer: pygments.lexer.Lexer, text: str) -> Iterator[Set[str]]:
@@ -785,11 +799,14 @@ def encoding_for(
             # File is empty, assume a dummy encoding.
             result = "utf-8"
         if result is None:
-            # Check for known BOMs.
-            for bom, encoding in _BOM_TO_ENCODING_MAP.items():
-                if heading[: len(bom)] == bom:
-                    result = encoding
-                    break
+            result = next(
+                (
+                    encoding_for_bom
+                    for bom, encoding_for_bom in _BOM_TO_ENCODING_MAP.items()
+                    if heading[: len(bom)] == bom
+                ),
+                None,
+            )
         if result is None:
             # Look for common headings that indicate the encoding.
             ascii_heading = heading.decode("ascii", errors="replace")
diff --git a/pygount/command.py b/pygount/command.py
index fe4d356..72b8276 100644
--- a/pygount/command.py
+++ b/pygount/command.py
@@ -39,7 +39,7 @@
  existing default values."""
 
 _HELP_FORMAT = 'output format, one of: {}; default: "%(default)s"'.format(
-    ", ".join(['"' + format + '"' for format in VALID_OUTPUT_FORMATS])
+    ", ".join(['"' + output_format + '"' for output_format in VALID_OUTPUT_FORMATS])
 )
 
 _HELP_GENERATED = """comma separated list of regular expressions to detect
@@ -86,11 +86,9 @@ def _check_encoding(name, encoding_to_check, alternative_encoding, source=None):
             "".encode(encoding_to_check)
         except LookupError:
             raise pygount.common.OptionError(
-                '{} is "{}" but must be "{}" or a known Python encoding'.format(
-                    name, encoding_to_check, alternative_encoding
-                ),
+                f'{name} is "{encoding_to_check}" but must be "{alternative_encoding}" or a known Python encoding',
                 source,
-            )
+            ) from None
 
 
 class Command:
@@ -120,14 +118,13 @@ def set_encodings(self, encoding, source=None):
         if encoding in ("automatic", "chardet"):
             default_encoding = encoding
             fallback_encoding = None
+        elif encoding.startswith(("automatic;", "chardet;")):
+            first_encoding_semicolon_index = encoding.find(";")
+            default_encoding = encoding[:first_encoding_semicolon_index]
+            fallback_encoding = encoding[first_encoding_semicolon_index + 1 :]
         else:
-            if encoding.startswith("automatic;") or encoding.startswith("chardet;"):
-                first_encoding_semicolon_index = encoding.find(";")
-                default_encoding = encoding[:first_encoding_semicolon_index]
-                fallback_encoding = encoding[first_encoding_semicolon_index + 1 :]
-            else:
-                default_encoding = encoding
-                fallback_encoding = pygount.analysis.DEFAULT_FALLBACK_ENCODING
+            default_encoding = encoding
+            fallback_encoding = pygount.analysis.DEFAULT_FALLBACK_ENCODING
         self.set_default_encoding(default_encoding, source)
         self.set_fallback_encoding(fallback_encoding, source)
 
@@ -350,25 +347,26 @@ def execute(self):
             target_context_manager = (
                 contextlib.nullcontext(sys.stdout)
                 if is_stdout
-                else open(self.output, "w", encoding="utf-8", newline="")
+                else open(self.output, "w", encoding="utf-8", newline="")  # noqa: SIM115
             )
-            with target_context_manager as target_file, writer_class(target_file) as writer:
-                with Progress(disable=not writer.has_to_track_progress, transient=True) as progress:
-                    try:
-                        for source_path, group in progress.track(source_paths_and_groups_to_analyze):
-                            writer.add(
-                                pygount.analysis.SourceAnalysis.from_file(
-                                    source_path,
-                                    group,
-                                    self.default_encoding,
-                                    self.fallback_encoding,
-                                    generated_regexes=self._generated_regexs,
-                                    duplicate_pool=duplicate_pool,
-                                    merge_embedded_language=self.has_to_merge_embedded_languages,
-                                )
+            with target_context_manager as target_file, writer_class(target_file) as writer, Progress(
+                disable=not writer.has_to_track_progress, transient=True
+            ) as progress:
+                try:
+                    for source_path, group in progress.track(source_paths_and_groups_to_analyze):
+                        writer.add(
+                            pygount.analysis.SourceAnalysis.from_file(
+                                source_path,
+                                group,
+                                self.default_encoding,
+                                self.fallback_encoding,
+                                generated_regexes=self._generated_regexs,
+                                duplicate_pool=duplicate_pool,
+                                merge_embedded_language=self.has_to_merge_embedded_languages,
                             )
-                    finally:
-                        progress.stop()
+                        )
+                finally:
+                    progress.stop()
 
 
 def pygount_command(arguments=None):
diff --git a/pygount/common.py b/pygount/common.py
index 399dea3..cf4d95f 100644
--- a/pygount/common.py
+++ b/pygount/common.py
@@ -25,8 +25,6 @@ class Error(Exception):
     Error to indicate that something went wrong during a pygount run.
     """
 
-    pass
-
 
 class OptionError(Error):
     """
@@ -55,10 +53,7 @@ def as_list(items_or_text: Union[str, Sequence[str]]) -> List[str]:
 def regex_from(pattern: Union[str, Pattern], is_shell_pattern=False) -> Pattern:
     assert pattern is not None
     if isinstance(pattern, str):
-        if is_shell_pattern:
-            result = re.compile(fnmatch.translate(pattern))
-        else:
-            result = re.compile(pattern)
+        result = re.compile(fnmatch.translate(pattern)) if is_shell_pattern else re.compile(pattern)
     else:
         result = pattern  # Assume pattern already is a compiled regular expression
     return result
@@ -86,20 +81,19 @@ def regexes_from(
                 patterns_text_without_prefixes = patterns_text_without_prefixes[len(ADDITIONAL_PATTERN) :]
 
             patterns = as_list(patterns_text_without_prefixes)
-            for pattern in patterns:
-                result.append(regex_from(pattern, is_shell_pattern))
+            result = [regex_from(pattern, is_shell_pattern) for pattern in patterns]
         else:
             regexes = list(patterns_text)
             if len(regexes) >= 1 and regexes[0] is None:
                 default_regexes = regexes_from(default_patterns_text)
                 regexes = regexes[1:]
             for supposed_regex in regexes:
-                assert isinstance(supposed_regex, _REGEX_TYPE), (
-                    "patterns_text must a text or sequence or regular expressions but contains: %a" % supposed_regex
-                )
+                assert isinstance(
+                    supposed_regex, _REGEX_TYPE
+                ), f"patterns_text must a text or sequence or regular expressions but contains: {supposed_regex}"
             result.extend(regexes)
     except re.error as error:
-        raise OptionError(f"cannot parse pattern for regular repression: {error}", source)
+        raise OptionError(f"cannot parse pattern for regular repression: {error}", source) from None
     result.extend(default_regexes)
     return result
 
@@ -157,7 +151,7 @@ def new_func1(*args, **kwargs):
             return new_func1
 
         return decorator
-    elif inspect.isclass(reason) or inspect.isfunction(reason):
+    if inspect.isclass(reason) or inspect.isfunction(reason):
         # The @deprecated is used without any 'reason'.
         #
         # .. code-block:: python
@@ -167,11 +161,7 @@ def new_func1(*args, **kwargs):
         #      pass
 
         func2 = reason
-
-        if inspect.isclass(func2):
-            fmt2 = "Call to deprecated class {name}."
-        else:
-            fmt2 = "Call to deprecated function {name}."
+        fmt2 = "Call to deprecated class {name}." if inspect.isclass(func2) else "Call to deprecated function {name}."
 
         @functools.wraps(func2)
         def new_func2(*args, **kwargs):
@@ -181,5 +171,10 @@ def new_func2(*args, **kwargs):
             return func2(*args, **kwargs)
 
         return new_func2
-    else:
-        raise TypeError(repr(type(reason)))
+    raise TypeError(repr(type(reason)))
+
+
+def mapped_repr(type_, name_to_value_map) -> str:
+    result = ", ".join(f"{name}={value}" for name, value in name_to_value_map.items())
+    result = f"{type_.__class__.__name__}({result})"
+    return result
diff --git a/pygount/git_storage.py b/pygount/git_storage.py
index 6ff8548..eb3e50f 100644
--- a/pygount/git_storage.py
+++ b/pygount/git_storage.py
@@ -13,6 +13,7 @@
 
 
 def git_remote_url_and_revision_if_any(git_url: str) -> Tuple[Optional[str], Optional[str]]:
+    assert git_url is not None
     git_url_match = _GIT_URL_REGEX.match(git_url)
     return (
         (None, None) if git_url_match is None else (git_url_match.group("remote_url"), git_url_match.group("revision"))
diff --git a/pygount/summary.py b/pygount/summary.py
index 9bce845..7d78b87 100644
--- a/pygount/summary.py
+++ b/pygount/summary.py
@@ -9,6 +9,7 @@
 from typing import Dict, Hashable
 
 from .analysis import SourceAnalysis
+from .common import mapped_repr
 
 _PSEUDO_LANGUAGE_REGEX = re.compile("^__[a-z]+__$")
 
@@ -139,12 +140,22 @@ def update_file_percentage(self, project_summary: "ProjectSummary"):
         self._has_up_to_date_percentages = True
 
     def __repr__(self):
-        result = f"{self.__class__.__name__}(language={self.language!r}, file_count={self.file_count}"
+        name_to_value_map = {
+            "language": f"{self.language!r}",
+            "file_count": self.file_count,
+        }
         if not self.is_pseudo_language:
-            result += ", code_count={}, documentation_count={}, empty_count={}, string_count={}".format(
-                self.code_count, self.documentation_count, self.empty_count, self.string_count
+            name_to_value_map.update(
+                {
+                    "code_count": self.code_count,
+                    "documentation_count": self.documentation_count,
+                    "empty_count": self.empty_count,
+                    "string_count": self.string_count,
+                }
             )
-        result += ")"
+        return mapped_repr(self, name_to_value_map)
+        result = ", ".join(f"{name}={value}" for name, value in name_to_value_map.items())
+        result = f"{self.__class__.__name__}({result})"
         return result
 
 
diff --git a/pygount/write.py b/pygount/write.py
index e9fdc1c..e61b4ea 100644
--- a/pygount/write.py
+++ b/pygount/write.py
@@ -118,11 +118,11 @@ def add(self, source_analysis: SourceAnalysis):
     def close(self):
         super().close()
         # Add various statistics to <header>.
-        ElementTree.SubElement(self._header_element, "elapsed_seconds", text="%.f" % self.duration_in_seconds)
-        ElementTree.SubElement(self._header_element, "n_files", text="%d" % self.project_summary.total_file_count)
-        ElementTree.SubElement(self._header_element, "n_lines", text="%d" % self.project_summary.total_line_count)
-        ElementTree.SubElement(self._header_element, "files_per_second", text="%.f" % self.files_per_second)
-        ElementTree.SubElement(self._header_element, "lines_per_second", text="%.f" % self.lines_per_second)
+        ElementTree.SubElement(self._header_element, "elapsed_seconds", text=str(self.duration_in_seconds))
+        ElementTree.SubElement(self._header_element, "n_files", text=str(self.project_summary.total_file_count))
+        ElementTree.SubElement(self._header_element, "n_lines", text=str(self.project_summary.total_line_count))
+        ElementTree.SubElement(self._header_element, "files_per_second", text=f"{self.files_per_second:f}")
+        ElementTree.SubElement(self._header_element, "lines_per_second", text=f"{self.lines_per_second:f}")
         ElementTree.SubElement(self._header_element, "report_file", text=self.target_name)
 
         # Add totals to <files>.
diff --git a/pygount/xmldialect.py b/pygount/xmldialect.py
index 70b01e5..3dda5d2 100644
--- a/pygount/xmldialect.py
+++ b/pygount/xmldialect.py
@@ -23,13 +23,11 @@
 _log = logging.getLogger("pygount")
 
 
-class SaxParserDone(Exception):  # noqa: N818
+class SaxParserDone(Exception):
     """
     Pseudo error to indicate that the Sax parser ist done.
     """
 
-    pass
-
 
 class XmlDialectHandler(xml.sax.ContentHandler, xml.sax.handler.DTDHandler):
     def __init__(self, max_element_count=100):
@@ -43,7 +41,7 @@ def _set_dialect_and_stop_parsing(self, dialect):
         self.dialect = dialect
         raise SaxParserDone(f"language detected: {dialect}")
 
-    def startElement(self, name, attrs):  # noqa: N802
+    def startElement(self, name, attrs):
         self._element_count += 1
         if self._element_count == self._max_element_count:
             raise SaxParserDone(f"no language found after parsing {self._element_count} elements")
@@ -60,7 +58,7 @@ def startElement(self, name, attrs):  # noqa: N802
         elif xmlns.startswith("http://www.netbeans.org/ns/project/"):
             self._set_dialect_and_stop_parsing("NetBeans Project")
 
-    def endElement(self, name):  # noqa: N802
+    def endElement(self, name):
         self._path = self._path[: -len(name) - 1]
 
 
diff --git a/pyproject.toml b/pyproject.toml
index 81151d8..c72df70 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,28 +1,3 @@
-[tool.black]
-line-length = 120
-include = '\.pyi?$'
-target-version = [
-    "py38",
-    "py39",
-    "py310",
-    "py311",
-    "py312",
-]
-exclude = '''
-/(
-    \.eggs
-  | \.git
-  | build
-  | dist
-)/
-'''
-
-[tool.isort]
-profile = "black"
-line_length = 120
-default_section = "THIRDPARTY"
-known_first_party = "pygount,scripts"
-
 [tool.pytest.ini_options]
 minversion = "7.2"
 addopts = [
@@ -34,7 +9,7 @@ testpaths = [
 
 [tool.poetry]
 name = "pygount"
-version = "1.8.0"
+version = "1.8.1"
 description = "count source lines of code (SLOC) using pygments"
 readme = "README.md"
 authors = ["Thomas Aglassinger <roskakori@users.sourceforge.net>"]
@@ -74,8 +49,9 @@ coveralls = "^4"
 coverage = "^7"
 pytest = "^8"
 pytest-cov = "^5"
-pre-commit = "^3"
+pre-commit = "^3.5"  # TODO#158: Change to "^3".
 pur = "^7"
+ruff = "^0.5.1"
 twine = "^5"
 
 [tool.poetry.group.docs]
@@ -91,8 +67,122 @@ pygount = "pygount.command:main"
 "Issue Tracker" = "https://github.com/roskakori/pygount/issues"
 "Changes" = "https://pygount.readthedocs.io/en/latest/changes.html"
 
-[pycodestyle]
-max-line-length = 120
+[tool.ruff]
+exclude = [
+    ".eggs",
+    ".git",
+    ".pytest_cache",
+    ".pytype",
+    ".ruff_cache",
+    ".vscode",
+    "__pypackages__",
+    "_build",
+    "build",
+    "dist",
+    "htmlcov",
+]
+line-length = 120
+target-version = "py38"
+
+[tool.ruff.lint]
+ignore = [
+    # Missing trailing comma → May cause conflicts when used with the formatter.
+    "COM812",
+    # Too many branches
+    "PLR0912",
+    # Too many arguments in function definition
+    "PLR0913",
+    # Too many statements
+    "PLR0915",
+    # Magic value used in comparison
+    "PLR2004",
+    # TODO#89 Enable checks for usage of pathlib.
+    "PTH100",
+    "PTH103",
+    "PTH107",
+    "PTH109",
+    "PTH110",
+    "PTH112",
+    "PTH114",
+    "PTH118",
+    "PTH119",
+    "PTH120",
+    "PTH122",
+    "PTH123",
+    "PTH202",
+    "PTH207",
+    # Unneccesarry assign → We regularly use `result = ...; return result` to examine the result in the debugger.
+    "RET504",
+    # TODO#506 Enable RUF012 check for mutable class attributes.
+    # Mutable class attributes should be annotated with `typing.ClassVar`
+    "RUF012",
+    # Avoid specifying long messages outside the exception class
+    "TRY003",
+    # Abstract `raise` to an inner function
+    "TRY301",
+]
+select = [
+    # flake8-builtins
+    "A",
+    # flake8-bugbear
+    "B",
+    # flake8-commas
+    "COM",
+    # flake8-comprehensions
+    "C4",
+    # flake8-django
+    "DJ",
+    # flake8-datetimez
+    "DTZ",
+    # pycodestyle
+    "E",
+    # Pyflakes
+    "F",
+    # isort
+    "I",
+    # flake8-no-pep420
+    "INP",
+    #  flake8-gettext
+    "INT",
+    # flake8-logging
+    "LOG",
+    # perflint
+    "PERF",
+    # pygrep-hooks
+    "PGH",
+    # flake8-pie
+    "PIE",
+    # pylint
+    "PL",
+    # flake8-use-pathlib
+    "PTH",
+    # refactor
+    "R",
+    # flake8-raise
+    "RSE",
+    # flake8-return
+    "RET",
+    # ruff specific rules
+    "RUF",
+    # flake8-self
+    "SLF",
+    # flake8-simplify
+    "SIM",
+    # tryceratops
+    "TRY",
+    # flake8-debugger
+    "T10",
+    # flake8-print
+    "T20",
+    # pyupgrade
+    "UP",
+]
+
+[tool.ruff.lint.isort]
+known-first-party = ["pygount", "scripts", "tests"]
+
+[tool.ruff.lint.per-file-ignores]
+"docs/conf.py" = ["INP001"]
 
 [build-system]
 requires = ["poetry-core>=1.8.1"]


Test patch:
diff --git a/tests/test_analysis.py b/tests/test_analysis.py
index ccbb5b5..5fe4af6 100644
--- a/tests/test_analysis.py
+++ b/tests/test_analysis.py
@@ -15,7 +15,14 @@
 
 from pygount import Error as PygountError
 from pygount import analysis, common
-from pygount.analysis import base_language, guess_lexer
+from pygount.analysis import (
+    _BOM_TO_ENCODING_MAP,
+    _delined_tokens,
+    _line_parts,
+    _pythonized_comments,
+    base_language,
+    guess_lexer,
+)
 
 from ._common import PYGOUNT_PROJECT_FOLDER, PYGOUNT_SOURCE_FOLDER, TempFolderTest
 from .test_xmldialect import EXAMPLE_ANT_CODE
@@ -75,16 +82,16 @@ def test_can_find_files_from_mixed_cloned_git_remote_url_and_local(self):
 
 class AnalysisTest(unittest.TestCase):
     def test_can_deline_tokens(self):
-        assert list(analysis._delined_tokens([(token.Comment, "# a")])) == [(token.Comment, "# a")]
-        assert list(analysis._delined_tokens([(token.Comment, "# a\n#  b")])) == [
+        assert list(_delined_tokens([(token.Comment, "# a")])) == [(token.Comment, "# a")]
+        assert list(_delined_tokens([(token.Comment, "# a\n#  b")])) == [
             (token.Comment, "# a\n"),
             (token.Comment, "#  b"),
         ]
-        assert list(analysis._delined_tokens([(token.Comment, "# a\n#  b\n")])) == [
+        assert list(_delined_tokens([(token.Comment, "# a\n#  b\n")])) == [
             (token.Comment, "# a\n"),
             (token.Comment, "#  b\n"),
         ]
-        assert list(analysis._delined_tokens([(token.Comment, "# a\n#  b\n # c\n")])) == [
+        assert list(_delined_tokens([(token.Comment, "# a\n#  b\n # c\n")])) == [
             (token.Comment, "# a\n"),
             (token.Comment, "#  b\n"),
             (token.Comment, " # c\n"),
@@ -92,13 +99,13 @@ def test_can_deline_tokens(self):
 
     def test_can_compute_python_line_parts(self):
         python_lexer = lexers.get_lexer_by_name("python")
-        assert list(analysis._line_parts(python_lexer, "#")) == [set("d")]
-        assert list(analysis._line_parts(python_lexer, "s = 'x'  # x")) == [set("cds")]
+        assert list(_line_parts(python_lexer, "#")) == [set("d")]
+        assert list(_line_parts(python_lexer, "s = 'x'  # x")) == [set("cds")]
 
     def test_can_detect_white_text(self):
         python_lexer = lexers.get_lexer_by_name("python")
-        assert list(analysis._line_parts(python_lexer, "{[()]};")) == [set()]
-        assert list(analysis._line_parts(python_lexer, "pass")) == [set()]
+        assert list(_line_parts(python_lexer, "{[()]};")) == [set()]
+        assert list(_line_parts(python_lexer, "pass")) == [set()]
 
     def test_can_convert_python_strings_to_comments(self):
         source_code = (
@@ -106,14 +113,14 @@ def test_can_convert_python_strings_to_comments(self):
         )
         python_lexer = lexers.get_lexer_by_name("python")
         python_tokens = python_lexer.get_tokens(source_code)
-        for token_type, token_text in list(analysis._pythonized_comments(analysis._delined_tokens(python_tokens))):
+        for token_type, _ in list(_pythonized_comments(_delined_tokens(python_tokens))):
             assert token_type not in token.String
 
     @staticmethod
     def _line_parts(lexer_name: str, source_lines: List[str]) -> List[Set[str]]:
         lexer = lexers.get_lexer_by_name(lexer_name)
         source_code = "\n".join(source_lines)
-        return list(analysis._line_parts(lexer, source_code))
+        return list(_line_parts(lexer, source_code))
 
     def test_can_analyze_python(self):
         source_lines = [
@@ -327,7 +334,7 @@ def test_can_use_deprecated_counts():
 
 
 class EncodingTest(TempFolderTest):
-    _ENCODING_TO_BOM_MAP = {encoding: bom for bom, encoding in analysis._BOM_TO_ENCODING_MAP.items()}
+    _ENCODING_TO_BOM_MAP = {encoding: bom for bom, encoding in _BOM_TO_ENCODING_MAP.items()}
     _TEST_CODE = "x = '\u00fd \u20ac'"
 
     def _test_can_detect_bom_encoding(self, encoding):
@@ -341,7 +348,7 @@ def _test_can_detect_bom_encoding(self, encoding):
         assert actual_encoding == encoding
 
     def test_can_detect_bom_encodings(self):
-        for _, encoding in analysis._BOM_TO_ENCODING_MAP.items():
+        for encoding in _BOM_TO_ENCODING_MAP.values():
             self._test_can_detect_bom_encoding(encoding)
 
     def test_can_detect_plain_encoding(self):
@@ -389,9 +396,8 @@ def test_can_use_hardcoded_ending(self):
         actual_encoding = analysis.encoding_for(test_path, "utf-8")
         assert actual_encoding == "utf-8"
         # Make sure that we cannot actually read the file using the hardcoded but wrong encoding.
-        with open(test_path, encoding=actual_encoding) as broken_test_file:
-            with pytest.raises(UnicodeDecodeError):
-                broken_test_file.read()
+        with open(test_path, encoding=actual_encoding) as broken_test_file, pytest.raises(UnicodeDecodeError):
+            broken_test_file.read()
 
     def test_can_detect_binary_with_zero_byte(self):
         test_path = self.create_temp_binary_file("binary", b"hello\0world")
@@ -407,9 +413,7 @@ class GeneratedCodeTest(TempFolderTest):
     # Example code for
     # generated source code.
     print("I'm generated!")
-    """.split(
-        "\n"
-    )
+    """.split("\n")
     _STANDARD_GENERATED_REGEXES = common.regexes_from(
         common.REGEX_PATTERN_PREFIX + ".*some,.*other,.*generated,.*print"
     )
diff --git a/tests/test_command.py b/tests/test_command.py
index ba81d3b..e07c0d7 100644
--- a/tests/test_command.py
+++ b/tests/test_command.py
@@ -2,6 +2,8 @@
 Tests for pygount command line interface.
 """
 
+import contextlib
+
 # Copyright (c) 2016-2024, Thomas Aglassinger.
 # All rights reserved. Distributed under the BSD License.
 import json
@@ -35,10 +37,8 @@ def test_can_set_encoding(self):
 
     def test_can_execute_on_own_code(self):
         output_path = os.path.join(self.tests_temp_folder, "test_can_execute_on_own_code.txt")
-        try:
+        with contextlib.suppress(FileNotFoundError):  # Ignore missing file as it is going to be recreated.
             os.remove(output_path)
-        except FileNotFoundError:  # pragma: no cover
-            pass  # Ignore missing file as it is going to be recreated.
         command = Command()
         command.set_output(output_path)
         command.set_output_format("cloc-xml")
diff --git a/tests/test_common.py b/tests/test_common.py
index 46b497b..ae04338 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -78,7 +78,7 @@ def test_can_represent_text_as_list():
 def test_can_represent_iterable_as_list():
     assert pygount.common.as_list([]) == []
     assert pygount.common.as_list(["a", 1, None]) == ["a", 1, None]
-    assert pygount.common.as_list(tuple()) == []
+    assert pygount.common.as_list(()) == []
     assert pygount.common.as_list(range(3)) == [0, 1, 2]
 
 
@@ -101,3 +101,14 @@ def test_can_convert_multiple_lines():
 
 def test_can_convert_empty_lines():
     assert list(pygount.common.lines("\n\n\n")) == ["", "", ""]
+
+
+def test_can_compute_mapped_repr():
+    class Dummy:
+        pass
+
+    assert pygount.common.mapped_repr(Dummy(), {}) == "Dummy()"
+    assert (
+        pygount.common.mapped_repr(Dummy(), {"some": "such", "other": 1, "whatever": True})
+        == "Dummy(some=such, other=1, whatever=True)"
+    )
diff --git a/tests/test_lexers.py b/tests/test_lexers.py
index 7229d65..b9d29d1 100644
--- a/tests/test_lexers.py
+++ b/tests/test_lexers.py
@@ -24,7 +24,6 @@ def test_can_lex_idl():
         ]
     )
     text_tokens = list(lexer.get_tokens(text))
-    print(text_tokens)
     assert text_tokens == [
         (token.Token.Comment.Multiline, "/* some\n * comment */"),
         (token.Token.Text.Whitespace, "\n"),
diff --git a/tests/test_write.py b/tests/test_write.py
index fdbfec6..fb2f395 100644
--- a/tests/test_write.py
+++ b/tests/test_write.py
@@ -22,10 +22,11 @@ def test_can_collect_totals():
         analysis.SourceAnalysis("some.py", "Python", "some", 1, 2, 3, 4, analysis.SourceState.analyzed, None),
         analysis.SourceAnalysis("other.py", "Python", "some", 10, 20, 30, 40, analysis.SourceState.analyzed, None),
     )
-    with tempfile.NamedTemporaryFile("w", encoding="utf-8", prefix="pygount_", suffix=".tmp") as target_stream:
-        with write.BaseWriter(target_stream) as writer:
-            for source_analysis in source_analyses:
-                writer.add(source_analysis)
+    with tempfile.NamedTemporaryFile(
+        "w", encoding="utf-8", prefix="pygount_", suffix=".tmp"
+    ) as target_stream, write.BaseWriter(target_stream) as writer:
+        for source_analysis in source_analyses:
+            writer.add(source_analysis)
     assert writer.project_summary.total_file_count == 2
     assert writer.project_summary.total_line_count == 110
     assert writer.duration_in_seconds > 0
@@ -113,8 +114,7 @@ def _summary_lines_for(self, source_analyses):
         # actual data are only available during close() at which point they
         # would not be accessible to StringIO.getvalue().
         summary_path = Path(self.tests_temp_folder, "summary.tmp")
-        with summary_path.open("w", encoding="utf-8") as summary_file:
-            with write.SummaryWriter(summary_file) as writer:
-                for source_analysis in source_analyses:
-                    writer.add(source_analysis)
+        with summary_path.open("w", encoding="utf-8") as summary_file, write.SummaryWriter(summary_file) as writer:
+            for source_analysis in source_analyses:
+                writer.add(source_analysis)
         return summary_path.read_text("utf-8").splitlines()