diffpdf 0.3.1__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {diffpdf-0.3.1 → diffpdf-1.0.0}/.github/dependabot.yml +1 -1
  2. diffpdf-1.0.0/.github/workflows/build.yml +70 -0
  3. diffpdf-1.0.0/.vscode/extensions.json +16 -0
  4. diffpdf-1.0.0/.vscode/settings.json +12 -0
  5. {diffpdf-0.3.1 → diffpdf-1.0.0}/PKG-INFO +37 -17
  6. {diffpdf-0.3.1 → diffpdf-1.0.0}/README.md +31 -7
  7. {diffpdf-0.3.1 → diffpdf-1.0.0}/hooks/pre-commit +29 -15
  8. diffpdf-1.0.0/pyproject.toml +75 -0
  9. diffpdf-1.0.0/src/diffpdf/__init__.py +49 -0
  10. {diffpdf-0.3.1 → diffpdf-1.0.0}/src/diffpdf/cli.py +13 -10
  11. diffpdf-1.0.0/src/diffpdf/logger.py +24 -0
  12. {diffpdf-0.3.1 → diffpdf-1.0.0}/src/diffpdf/page_check.py +3 -1
  13. diffpdf-1.0.0/src/diffpdf/py.typed +0 -0
  14. {diffpdf-0.3.1 → diffpdf-1.0.0}/src/diffpdf/text_check.py +3 -1
  15. {diffpdf-0.3.1 → diffpdf-1.0.0}/src/diffpdf/visual_check.py +8 -2
  16. diffpdf-1.0.0/tests/test_api.py +30 -0
  17. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/test_cli.py +13 -15
  18. diffpdf-1.0.0/uv.lock +694 -0
  19. diffpdf-0.3.1/.github/workflows/build.yml +0 -35
  20. diffpdf-0.3.1/.vscode/settings.json +0 -7
  21. diffpdf-0.3.1/pyproject.toml +0 -55
  22. diffpdf-0.3.1/src/diffpdf/__init__.py +0 -27
  23. diffpdf-0.3.1/src/diffpdf/comparators.py +0 -31
  24. diffpdf-0.3.1/src/diffpdf/logger.py +0 -45
  25. diffpdf-0.3.1/tests/test_api.py +0 -12
  26. diffpdf-0.3.1/tests/test_comparators.py +0 -48
  27. {diffpdf-0.3.1 → diffpdf-1.0.0}/.github/workflows/pypi-publish.yml +0 -0
  28. {diffpdf-0.3.1 → diffpdf-1.0.0}/.gitignore +0 -0
  29. {diffpdf-0.3.1 → diffpdf-1.0.0}/LICENSE +0 -0
  30. {diffpdf-0.3.1 → diffpdf-1.0.0}/MANIFEST.in +0 -0
  31. {diffpdf-0.3.1 → diffpdf-1.0.0}/src/diffpdf/hash_check.py +0 -0
  32. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/fail/1-letter-diff-A.pdf +0 -0
  33. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/fail/1-letter-diff-B.pdf +0 -0
  34. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/fail/major-color-diff-A.pdf +0 -0
  35. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/fail/major-color-diff-B.pdf +0 -0
  36. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/fail/page-count-diff-A.pdf +0 -0
  37. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/fail/page-count-diff-B.pdf +0 -0
  38. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/hash-diff-A.pdf +0 -0
  39. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/hash-diff-B.pdf +0 -0
  40. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/identical-A.pdf +0 -0
  41. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/identical-B.pdf +0 -0
  42. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/minor-color-diff-A.pdf +0 -0
  43. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/minor-color-diff-B.pdf +0 -0
  44. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/multiplatform-diff-A.pdf +0 -0
  45. {diffpdf-0.3.1 → diffpdf-1.0.0}/tests/assets/pass/multiplatform-diff-B.pdf +0 -0
@@ -1,6 +1,6 @@
1
1
  version: 2
2
2
  updates:
3
- - package-ecosystem: "pip"
3
+ - package-ecosystem: "uv"
4
4
  directory: "/"
5
5
  schedule:
6
6
  interval: "weekly"
@@ -0,0 +1,70 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ matrix:
13
+ # Tests compatibility across Ubuntu/Windows and Python/package version extremes
14
+ os: [ubuntu-latest, windows-latest]
15
+ dependencies: [locked, oldest]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+
20
+ # When searching for a system Python version, uv will use the first compatible version - not the newest version.
21
+ # Therefore, make sure the newest version is available on the runner.
22
+ - name: Install Python
23
+ uses: actions/setup-python@v6
24
+ with:
25
+ python-version: 3.x
26
+
27
+ # At the time of writing there is no way to force uv select the lowest version of Python.
28
+ # Therefore, extract this from pyproject.toml
29
+ # More info: https://github.com/astral-sh/uv/issues/16333
30
+ - name: Extract minimum Python version from pyproject.toml
31
+ if: matrix.dependencies == 'oldest'
32
+ id: python-version
33
+ shell: bash
34
+ run: |
35
+ MIN_PY=$(grep 'requires-python' pyproject.toml | sed -E 's/[^0-9.]//g')
36
+ echo "min=$MIN_PY" >> $GITHUB_OUTPUT
37
+
38
+ - name: Install uv
39
+ uses: astral-sh/setup-uv@v7
40
+ with:
41
+ version: "latest"
42
+
43
+ - name: Install locked dependencies
44
+ if: matrix.dependencies == 'locked'
45
+ run: uv sync --locked
46
+
47
+ - name: Install oldest dependencies
48
+ if: matrix.dependencies == 'oldest'
49
+ run: uv sync --resolution lowest --python ${{ steps.python-version.outputs.min }}
50
+
51
+ - name: Run ruff (check)
52
+ if: matrix.dependencies == 'locked'
53
+ run: uv run ruff check
54
+
55
+ - name: Run ruff (format)
56
+ if: matrix.dependencies == 'locked'
57
+ run: uv run ruff format --check
58
+
59
+ - name: Run ty
60
+ if: matrix.dependencies == 'locked'
61
+ run: uv run ty check --output-format github
62
+
63
+ - name: Run pytest
64
+ run: uv run pytest
65
+
66
+ - name: Upload coverage reports to Codecov
67
+ if: matrix.os == 'ubuntu-latest' && matrix.dependencies == 'locked'
68
+ uses: codecov/codecov-action@v5
69
+ with:
70
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,16 @@
1
+ {
2
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4
+
5
+ // List of extensions which should be recommended for users of this workspace.
6
+ "recommendations": [
7
+ "ms-python.python",
8
+ "charliermarsh.ruff",
9
+ "ryanluker.vscode-coverage-gutters",
10
+ "astral-sh.ty"
11
+ ],
12
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
13
+ "unwantedRecommendations": [
14
+ "ms-python.vscode-pylance"
15
+ ]
16
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ // Format with Ruff on save & sort imports
3
+ "editor.formatOnSave": true,
4
+ "editor.defaultFormatter": "charliermarsh.ruff",
5
+ "editor.codeActionsOnSave": {
6
+ "source.organizeImports.ruff": "explicit"
7
+ },
8
+
9
+ // Configure Pytest
10
+ "python.testing.unittestEnabled": false,
11
+ "python.testing.pytestEnabled": true,
12
+ }
@@ -1,26 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffpdf
3
- Version: 0.3.1
3
+ Version: 1.0.0
4
4
  Summary: A tool for comparing PDF files
5
5
  Project-URL: Homepage, https://github.com/JustusRijke/DiffPDF
6
6
  Project-URL: Issues, https://github.com/JustusRijke/DiffPDF/issues
7
7
  Author-email: Justus Rijke <justusrijke@gmail.com>
8
8
  License-Expression: MIT
9
9
  License-File: LICENSE
10
- Classifier: Development Status :: 4 - Beta
10
+ Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Operating System :: Microsoft :: Windows
12
12
  Classifier: Operating System :: POSIX :: Linux
13
13
  Classifier: Programming Language :: Python :: 3
14
- Requires-Python: >=3.10
15
- Requires-Dist: click
16
- Requires-Dist: colorlog
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.10.0
16
+ Requires-Dist: click>=8
17
17
  Requires-Dist: pillow>=10.0.0
18
- Requires-Dist: pixelmatch-fast>=1.1.0
18
+ Requires-Dist: pixelmatch-fast>=1.3.1
19
19
  Requires-Dist: pymupdf>=1.23.0
20
- Provides-Extra: dev
21
- Requires-Dist: pytest; extra == 'dev'
22
- Requires-Dist: pytest-cov; extra == 'dev'
23
- Requires-Dist: ruff; extra == 'dev'
24
20
  Description-Content-Type: text/markdown
25
21
 
26
22
  # DiffPDF
@@ -29,6 +25,8 @@ Description-Content-Type: text/markdown
29
25
  [![codecov](https://codecov.io/gh/JustusRijke/DiffPDF/graph/badge.svg?token=O3ZJFG6X7A)](https://codecov.io/gh/JustusRijke/DiffPDF)
30
26
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue)](https://www.python.org/downloads/)
31
27
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
28
+ [![PyPI - Version](https://img.shields.io/pypi/v/DiffPDF)](https://pypi.org/project/DiffPDF/)
29
+ [![PyPI - Downloads](https://img.shields.io/pypi/dw/DiffPDF)](https://pypi.org/project/DiffPDF/)
32
30
 
33
31
  CLI tool for detecting structural, textual, and visual differences between PDF files, for use in automatic regression tests.
34
32
 
@@ -45,6 +43,8 @@ Each stage only runs if all previous stages pass.
45
43
 
46
44
  ## Installation
47
45
 
46
+ Install Python (v3.10 or higher) and install the package:
47
+
48
48
  ```bash
49
49
  pip install diffpdf
50
50
  ```
@@ -59,8 +59,7 @@ Options:
59
59
  --threshold FLOAT Pixelmatch threshold (0.0-1.0)
60
60
  --dpi INTEGER Render resolution
61
61
  --output-dir DIRECTORY Diff image output directory (optional, if not specified no diff images are saved)
62
- -v, --verbose Increase verbosity (-v for INFO, -vv for DEBUG)
63
- --save-log Write log output to log.txt
62
+ -v, --verbose Increase verbosity
64
63
  --version Show the version and exit.
65
64
  --help Show this message and exit.
66
65
  ```
@@ -79,16 +78,37 @@ from diffpdf import diffpdf
79
78
  # Basic usage (no diff images saved)
80
79
  diffpdf("reference.pdf", "actual.pdf")
81
80
 
82
- # With options (save diff images to ./output directory)
83
- diffpdf("reference.pdf", "actual.pdf", output_dir="./output", threshold=0.2, dpi=150, verbosity=2)
81
+ # With options (save diff images to ./output directory, extract higher quality images)
82
+ diffpdf("reference.pdf", "actual.pdf", output_dir="./output", dpi=300)
84
83
  ```
85
84
 
86
85
  ## Development
87
86
 
87
+ Install [uv](https://github.com/astral-sh/uv?tab=readme-ov-file#installation). Then, install dependencies & activate the automatically generated virtual environment:
88
+
89
+ ```bash
90
+ uv sync --locked
91
+ source .venv/bin/activate
92
+ ```
93
+
94
+ Skip `--locked` to use the newest dependencies (this might modify `uv.lock`)
95
+
96
+ Run tests:
97
+ ```bash
98
+ pytest
99
+ ```
100
+
101
+ Check code quality:
102
+ ```bash
103
+ ruff check
104
+ ruff format --check
105
+ ty check
106
+ ```
107
+
108
+ Better yet, install the [pre-commit](.git/hooks/pre-commit) hook, which runs code quality checks before every commit:
88
109
  ```bash
89
- pip install -e .[dev]
90
- pytest tests/ -v
91
- ruff check .
110
+ cp hooks/pre-commit .git/hooks/pre-commit
111
+ chmod +x .git/hooks/pre-commit
92
112
  ```
93
113
 
94
114
  ## Acknowledgements
@@ -4,6 +4,8 @@
4
4
  [![codecov](https://codecov.io/gh/JustusRijke/DiffPDF/graph/badge.svg?token=O3ZJFG6X7A)](https://codecov.io/gh/JustusRijke/DiffPDF)
5
5
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue)](https://www.python.org/downloads/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![PyPI - Version](https://img.shields.io/pypi/v/DiffPDF)](https://pypi.org/project/DiffPDF/)
8
+ [![PyPI - Downloads](https://img.shields.io/pypi/dw/DiffPDF)](https://pypi.org/project/DiffPDF/)
7
9
 
8
10
  CLI tool for detecting structural, textual, and visual differences between PDF files, for use in automatic regression tests.
9
11
 
@@ -20,6 +22,8 @@ Each stage only runs if all previous stages pass.
20
22
 
21
23
  ## Installation
22
24
 
25
+ Install Python (v3.10 or higher) and install the package:
26
+
23
27
  ```bash
24
28
  pip install diffpdf
25
29
  ```
@@ -34,8 +38,7 @@ Options:
34
38
  --threshold FLOAT Pixelmatch threshold (0.0-1.0)
35
39
  --dpi INTEGER Render resolution
36
40
  --output-dir DIRECTORY Diff image output directory (optional, if not specified no diff images are saved)
37
- -v, --verbose Increase verbosity (-v for INFO, -vv for DEBUG)
38
- --save-log Write log output to log.txt
41
+ -v, --verbose Increase verbosity
39
42
  --version Show the version and exit.
40
43
  --help Show this message and exit.
41
44
  ```
@@ -54,16 +57,37 @@ from diffpdf import diffpdf
54
57
  # Basic usage (no diff images saved)
55
58
  diffpdf("reference.pdf", "actual.pdf")
56
59
 
57
- # With options (save diff images to ./output directory)
58
- diffpdf("reference.pdf", "actual.pdf", output_dir="./output", threshold=0.2, dpi=150, verbosity=2)
60
+ # With options (save diff images to ./output directory, extract higher quality images)
61
+ diffpdf("reference.pdf", "actual.pdf", output_dir="./output", dpi=300)
59
62
  ```
60
63
 
61
64
  ## Development
62
65
 
66
+ Install [uv](https://github.com/astral-sh/uv?tab=readme-ov-file#installation). Then, install dependencies & activate the automatically generated virtual environment:
67
+
68
+ ```bash
69
+ uv sync --locked
70
+ source .venv/bin/activate
71
+ ```
72
+
73
+ Skip `--locked` to use the newest dependencies (this might modify `uv.lock`)
74
+
75
+ Run tests:
76
+ ```bash
77
+ pytest
78
+ ```
79
+
80
+ Check code quality:
81
+ ```bash
82
+ ruff check
83
+ ruff format --check
84
+ ty check
85
+ ```
86
+
87
+ Better yet, install the [pre-commit](.git/hooks/pre-commit) hook, which runs code quality checks before every commit:
63
88
  ```bash
64
- pip install -e .[dev]
65
- pytest tests/ -v
66
- ruff check .
89
+ cp hooks/pre-commit .git/hooks/pre-commit
90
+ chmod +x .git/hooks/pre-commit
67
91
  ```
68
92
 
69
93
  ## Acknowledgements
@@ -38,22 +38,36 @@ EOF
38
38
  exit 1
39
39
  fi
40
40
 
41
+ # Check if uv is installed
42
+ uv help -q
43
+ if [ $? -ne 0 ]; then
44
+ echo "'uv' is not installed or not in your PATH"
45
+ exit 1
46
+ fi
47
+
48
+ # Check if the project is synced
49
+ uv lock --check -q
50
+ if [ $? -ne 0 ]; then
51
+ echo "Error: Environment is out of sync. Please run 'uv sync'."
52
+ exit 1
53
+ fi
54
+
41
55
  # Ruff checks
42
- ruff check
43
- CHECK_EXIT=$?
44
-
45
- ruff format --check
46
- FORMAT_EXIT=$?
47
-
48
- if [ $CHECK_EXIT -ne 0 ] || [ $FORMAT_EXIT -ne 0 ]; then
49
- if [ $CHECK_EXIT -ne 0 ]; then
50
- echo "Ruff found linting errors. Run: ruff check --fix"
51
- fi
52
- if [ $FORMAT_EXIT -ne 0 ]; then
53
- echo "Ruff found formatting issues. Run: ruff format"
54
- fi
56
+ uv run ruff check -q
57
+ if [ $? -ne 0 ]; then
58
+ echo "Ruff found linting errors. Please run 'ruff check --fix'"
59
+ exit 1
60
+ fi
61
+
62
+ uv run ruff format --check -q
63
+ if [ $? -ne 0 ]; then
64
+ echo "Ruff found formatting issues. Please run 'ruff format'"
55
65
  exit 1
56
66
  fi
57
67
 
58
- # If there are whitespace errors, print the offending file names and fail.
59
- exec git diff-index --check --cached $against --
68
+ # Type annotation checks
69
+ uv run ty check -q
70
+ if [ $? -ne 0 ]; then
71
+ echo "Found type annotation errors. Please run 'ty check'"
72
+ exit 1
73
+ fi
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = [
3
+ "hatchling",
4
+ "hatch-vcs",
5
+ ]
6
+ build-backend = "hatchling.build"
7
+
8
+ [project]
9
+ name = "diffpdf"
10
+ dynamic = ["version"]
11
+ description = "A tool for comparing PDF files"
12
+ readme = "README.md"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ authors = [{name = "Justus Rijke", email="justusrijke@gmail.com"}]
16
+ requires-python = ">=3.10.0"
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = [
25
+ "click>=8",
26
+ "pymupdf>=1.23.0",
27
+ "pixelmatch-fast>=1.3.1",
28
+ "Pillow>=10.0.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/JustusRijke/DiffPDF"
33
+ Issues = "https://github.com/JustusRijke/DiffPDF/issues"
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "pytest>=9",
38
+ "pytest-cov>=6",
39
+ "ruff>=0.10",
40
+ "ty>=0.0.8",
41
+ ]
42
+
43
+ [project.scripts]
44
+ diffpdf = "diffpdf.cli:cli"
45
+
46
+ [tool.hatch.version]
47
+ source = "vcs"
48
+
49
+ [tool.hatch.version.raw-options]
50
+ local_scheme = "no-local-version"
51
+
52
+ [tool.pytest]
53
+ strict = true
54
+ testpaths = ["tests"]
55
+ filterwarnings = ["error"] # Treat all warnings as errors (e.g., deprecation warnings)
56
+ addopts = [
57
+ "-v", # Verbose output
58
+ "--cov", # Enable coverage
59
+ "--cov-branch", # Make sure to cover every decision branch
60
+ "--cov-report=term-missing", # Report which lines aren't covered
61
+ "--cov-report=xml", # Dump to XML for Codecov
62
+ ]
63
+
64
+ [tool.ruff.lint]
65
+ extend-select = [
66
+ "I", # Sort imports
67
+ "ANN", # Enforce type annotations
68
+ "PT", # Common style issues or inconsistencies with pytest-based tests
69
+ ]
70
+
71
+ [tool.ruff.lint.isort]
72
+ combine-as-imports = true # Combines "as" imports on the same line
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ "tests/**" = ["ANN"] # Do not enforce type annotations for tests
@@ -0,0 +1,49 @@
1
+ from importlib.metadata import version
2
+ from pathlib import Path
3
+
4
+ from .hash_check import check_hash
5
+ from .logger import setup_logging
6
+ from .page_check import check_page_counts
7
+ from .text_check import check_text_content
8
+ from .visual_check import check_visual_content
9
+
10
+ __version__ = version("diffpdf")
11
+
12
+
13
+ def diffpdf(
14
+ reference: str | Path,
15
+ actual: str | Path,
16
+ threshold: float = 0.1,
17
+ dpi: int = 96,
18
+ output_dir: str | Path | None = None,
19
+ verbose: bool = False,
20
+ ) -> bool:
21
+ ref_path = Path(reference) if isinstance(reference, str) else reference
22
+ actual_path = Path(actual) if isinstance(actual, str) else actual
23
+ out_path = Path(output_dir) if isinstance(output_dir, str) else output_dir
24
+
25
+ logger = setup_logging(verbose)
26
+
27
+ logger.info("[1/4] Checking file hashes...")
28
+ if check_hash(ref_path, actual_path):
29
+ logger.info("Files are identical (hash match)")
30
+ return True
31
+ logger.info("Hashes differ, continuing checks")
32
+
33
+ logger.info("[2/4] Checking page counts...")
34
+ if not check_page_counts(ref_path, actual_path):
35
+ return False
36
+
37
+ logger.info("[3/4] Checking text content...")
38
+ if not check_text_content(ref_path, actual_path):
39
+ return False
40
+
41
+ logger.info("[4/4] Checking visual content...")
42
+ if not check_visual_content(ref_path, actual_path, threshold, dpi, out_path):
43
+ return False
44
+
45
+ logger.info("PDFs are equivalent")
46
+ return True
47
+
48
+
49
+ __all__ = ["diffpdf", "__version__"]
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
 
4
4
  import click
5
5
 
6
- from .comparators import compare_pdfs
6
+ from . import diffpdf
7
7
  from .logger import setup_logging
8
8
 
9
9
 
@@ -25,22 +25,25 @@ from .logger import setup_logging
25
25
  @click.option(
26
26
  "-v",
27
27
  "--verbose",
28
- "verbosity",
29
- count=True,
30
- help="Increase verbosity (-v for INFO, -vv for DEBUG)",
28
+ is_flag=True,
29
+ help="Increase verbosity",
31
30
  )
32
- @click.option("--save-log", is_flag=True, help="Write log output to log.txt")
33
31
  @click.version_option(package_name="diffpdf")
34
- def cli(reference, actual, threshold, dpi, output_dir, verbosity, save_log):
32
+ def cli(
33
+ reference: Path,
34
+ actual: Path,
35
+ threshold: float,
36
+ dpi: int,
37
+ output_dir: Path | None,
38
+ verbose: bool,
39
+ ) -> None:
35
40
  """Compare two PDF files for structural, textual, and visual differences."""
36
- logger = setup_logging(verbosity, save_log)
37
- logger.debug("Debug logging enabled")
38
-
39
41
  try:
40
- if compare_pdfs(reference, actual, threshold, dpi, output_dir, logger):
42
+ if diffpdf(reference, actual, threshold, dpi, output_dir, verbose):
41
43
  sys.exit(0)
42
44
  else:
43
45
  sys.exit(1)
44
46
  except Exception as e: # pragma: no cover
47
+ logger = setup_logging(verbose)
45
48
  logger.critical(f"Error: {e}", exc_info=True)
46
49
  sys.exit(2)
@@ -0,0 +1,24 @@
1
+ import logging
2
+
3
+ LOG_FORMAT = (
4
+ "%(asctime)s %(levelname)-8s %(filename)s:%(lineno)d (%(funcName)s): %(message)s"
5
+ )
6
+ DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
7
+
8
+
9
+ def setup_logging(verbose: bool) -> logging.Logger:
10
+ if verbose:
11
+ level = logging.INFO
12
+ else:
13
+ level = logging.WARNING
14
+
15
+ formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT)
16
+
17
+ console_handler = logging.StreamHandler()
18
+ console_handler.setFormatter(formatter)
19
+
20
+ logger = logging.getLogger()
21
+ logger.setLevel(level)
22
+ logger.addHandler(console_handler)
23
+
24
+ return logger
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
 
3
4
  import fitz
@@ -10,7 +11,8 @@ def get_page_count(pdf_path: Path) -> int:
10
11
  return count
11
12
 
12
13
 
13
- def check_page_counts(ref: Path, actual: Path, logger) -> bool:
14
+ def check_page_counts(ref: Path, actual: Path) -> bool:
15
+ logger = logging.getLogger()
14
16
  ref_count = get_page_count(ref)
15
17
  actual_count = get_page_count(actual)
16
18
 
File without changes
@@ -1,4 +1,5 @@
1
1
  import difflib
2
+ import logging
2
3
  import re
3
4
  from pathlib import Path
4
5
  from typing import Iterable
@@ -32,7 +33,8 @@ def generate_diff(
32
33
  return diff
33
34
 
34
35
 
35
- def check_text_content(ref: Path, actual: Path, logger) -> bool:
36
+ def check_text_content(ref: Path, actual: Path) -> bool:
37
+ logger = logging.getLogger()
36
38
  # Extract text and remove whitespace
37
39
  ref_text = re.sub(r"\s+", " ", extract_text(ref)).strip()
38
40
  actual_text = re.sub(r"\s+", " ", extract_text(actual)).strip()
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
 
3
4
  import fitz
@@ -21,7 +22,7 @@ def compare_images(
21
22
  output_path: Path | None,
22
23
  ) -> bool:
23
24
  mismatch_count = pixelmatch(
24
- ref_img, actual_img, diff_path=output_path, threshold=threshold
25
+ ref_img, actual_img, output=output_path, threshold=threshold
25
26
  )
26
27
 
27
28
  if mismatch_count > 0:
@@ -31,8 +32,13 @@ def compare_images(
31
32
 
32
33
 
33
34
  def check_visual_content(
34
- ref: Path, actual: Path, threshold: float, dpi: int, output_dir: Path | None, logger
35
+ ref: Path,
36
+ actual: Path,
37
+ threshold: float,
38
+ dpi: int,
39
+ output_dir: Path | None,
35
40
  ) -> bool:
41
+ logger = logging.getLogger()
36
42
  if output_dir is not None:
37
43
  output_dir.mkdir(parents=True, exist_ok=True)
38
44
 
@@ -0,0 +1,30 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from diffpdf import diffpdf
6
+
7
+ TEST_ASSETS_DIR = Path(__file__).parent / "assets"
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ ("ref_pdf_rel", "actual_pdf_rel", "should_pass"),
12
+ [
13
+ # Pass cases
14
+ ("pass/identical-A.pdf", "pass/identical-B.pdf", True),
15
+ ("pass/hash-diff-A.pdf", "pass/hash-diff-B.pdf", True),
16
+ ("pass/minor-color-diff-A.pdf", "pass/minor-color-diff-B.pdf", True),
17
+ ("pass/multiplatform-diff-A.pdf", "pass/multiplatform-diff-B.pdf", True),
18
+ # Fail cases
19
+ ("fail/1-letter-diff-A.pdf", "fail/1-letter-diff-B.pdf", False),
20
+ ("fail/major-color-diff-A.pdf", "fail/major-color-diff-B.pdf", False),
21
+ ("fail/page-count-diff-A.pdf", "fail/page-count-diff-B.pdf", False),
22
+ ],
23
+ )
24
+ def test_api(ref_pdf_rel, actual_pdf_rel, should_pass):
25
+ ref_pdf = TEST_ASSETS_DIR / ref_pdf_rel
26
+ actual_pdf = TEST_ASSETS_DIR / actual_pdf_rel
27
+
28
+ result = diffpdf(ref_pdf, actual_pdf)
29
+
30
+ assert result == should_pass
@@ -7,30 +7,28 @@ from diffpdf.cli import cli
7
7
  TEST_ASSETS_DIR = Path(__file__).parent / "assets"
8
8
 
9
9
 
10
- def test_verbose_flag():
10
+ def test_cli_with_output_dir():
11
11
  runner = CliRunner()
12
- result = runner.invoke(
13
- cli,
14
- [
15
- str(TEST_ASSETS_DIR / "pass/identical-A.pdf"),
16
- str(TEST_ASSETS_DIR / "pass/identical-B.pdf"),
17
- "-v",
18
- ],
19
- )
20
- assert result.exit_code == 0
21
- assert "INFO" in result.output
22
- assert "DEBUG" not in result.output
23
12
 
13
+ with runner.isolated_filesystem():
14
+ ref_pdf = str(TEST_ASSETS_DIR / "fail/major-color-diff-A.pdf")
15
+ actual_pdf = str(TEST_ASSETS_DIR / "fail/major-color-diff-B.pdf")
16
+
17
+ result = runner.invoke(cli, [ref_pdf, actual_pdf, "--output-dir", "./diff"])
18
+
19
+ assert result.exit_code == 1
20
+ assert Path("./diff").exists()
24
21
 
25
- def test_double_verbose_flag():
22
+
23
+ def test_verbose_flag():
26
24
  runner = CliRunner()
27
25
  result = runner.invoke(
28
26
  cli,
29
27
  [
30
28
  str(TEST_ASSETS_DIR / "pass/identical-A.pdf"),
31
29
  str(TEST_ASSETS_DIR / "pass/identical-B.pdf"),
32
- "-vv",
30
+ "-v",
33
31
  ],
34
32
  )
35
33
  assert result.exit_code == 0
36
- assert "DEBUG" in result.output
34
+ assert "INFO" in result.output