proof-pdf 0.3.2__tar.gz → 0.3.3__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.
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/PKG-INFO +7 -1
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/README.md +6 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/pyproject.toml +1 -1
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/__init__.py +8 -3
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/cli.py +30 -24
- proof_pdf-0.3.3/src/proof_pdf/config.py +79 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/core.py +10 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_core.py +15 -0
- proof_pdf-0.3.3/tests/test_proof_pdf/test_load_profile.py +90 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_public_api.py +1 -1
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/LICENSE +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/NOTICE +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/__init__.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/colour.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/config.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/content.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/contrast.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/deltae.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/fonts.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/paint.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/proc.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/resolution.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/spaces.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/structure.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/text.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/_internal/verapdf.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/inventory.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/models.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/preview.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/py.typed +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/src/proof_pdf/texlog.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/build_pdfs.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/black-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/corrupt.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/cyan-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/edge-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/embedded-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/good.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/hairline.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/heavy-ink.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/high-contrast.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/low-contrast.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/low-dpi-image.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/no-bleed.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/no-output-intent.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/notdef-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/plain.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/registration-spot.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/rgb-image.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/rich-black-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/small-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/spot-ink.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/three-pages.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/transparent-matching.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/transparent-mismatch.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/transparent-undeclared.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/two-sizes.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/type3-text.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/white-overprint.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/pdfs/wrong-size.pdf +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/suites/README.md +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/fixtures/suites/expected.toml +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/__init__.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/conftest.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_bleed.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_cli.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_colour.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_config.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_content.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_contrast.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_deltae.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_fixtures_current.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_integration.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_inventory.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_paint.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_preview.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_proc.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_resolution.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_structure.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_suites.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_texlog.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_text.py +0 -0
- {proof_pdf-0.3.2 → proof_pdf-0.3.3}/tests/test_proof_pdf/test_verapdf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proof-pdf
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Hold print PDFs to PDF/X-4 and PDF/A: veraPDF, page boxes, geometry, ink and image resolution.
|
|
5
5
|
Keywords: pdf,pdf/x-4,pdf/a,verapdf,prepress,print,preflight,ghostscript,lualatex
|
|
6
6
|
Author-Email: Ilja Orlovs <ilja@release.art>
|
|
@@ -79,6 +79,12 @@ so nothing blocks the event loop.
|
|
|
79
79
|
As a library the API is async: `await proof_pdf.core.verify_file(path, profile)` returns a `FileReport` of `Finding`s, and
|
|
80
80
|
`await proof_pdf.core.verify_files(paths, profile, jobs=4)` returns one report per path in order. See `proof_pdf.models.Profile`.
|
|
81
81
|
|
|
82
|
+
A job's config file works from Python too. `proof_pdf.load_profile(path=None, *, profile=None)` reads it like the CLI does (the nearest
|
|
83
|
+
`proof-pdf.toml`, `PROOF_PDF_CONFIG`, `PROOF_PDF_PROFILE`) and returns a `LoadedConfig`: the `Profile`, the `exclude` globs, the `exempt`
|
|
84
|
+
map, and `jobs`, `timeout` and the tool names. A file the CLI would reject raises `proof_pdf.ConfigError`. Pass those to
|
|
85
|
+
`core.expand_targets` and `core.verify_files`, so the CLI and your code share one definition. `core.missing_tools(profile)` names the
|
|
86
|
+
tools the profile needs (veraPDF for PDF/A, Ghostscript for colour) that are not on `PATH`, so a caller can fail early with one message.
|
|
87
|
+
|
|
82
88
|
### Proofing
|
|
83
89
|
|
|
84
90
|
Beyond the standards, `verify` can hold a PDF to the rules a print job usually has. Missing glyphs and fonts are checked by
|
|
@@ -53,6 +53,12 @@ so nothing blocks the event loop.
|
|
|
53
53
|
As a library the API is async: `await proof_pdf.core.verify_file(path, profile)` returns a `FileReport` of `Finding`s, and
|
|
54
54
|
`await proof_pdf.core.verify_files(paths, profile, jobs=4)` returns one report per path in order. See `proof_pdf.models.Profile`.
|
|
55
55
|
|
|
56
|
+
A job's config file works from Python too. `proof_pdf.load_profile(path=None, *, profile=None)` reads it like the CLI does (the nearest
|
|
57
|
+
`proof-pdf.toml`, `PROOF_PDF_CONFIG`, `PROOF_PDF_PROFILE`) and returns a `LoadedConfig`: the `Profile`, the `exclude` globs, the `exempt`
|
|
58
|
+
map, and `jobs`, `timeout` and the tool names. A file the CLI would reject raises `proof_pdf.ConfigError`. Pass those to
|
|
59
|
+
`core.expand_targets` and `core.verify_files`, so the CLI and your code share one definition. `core.missing_tools(profile)` names the
|
|
60
|
+
tools the profile needs (veraPDF for PDF/A, Ghostscript for colour) that are not on `PATH`, so a caller can fail early with one message.
|
|
61
|
+
|
|
56
62
|
### Proofing
|
|
57
63
|
|
|
58
64
|
Beyond the standards, `verify` can hold a PDF to the rules a print job usually has. Missing glyphs and fonts are checked by
|
|
@@ -8,11 +8,16 @@ coverage, overprint, rich-black small text, stray RGB, effective image DPI).
|
|
|
8
8
|
The entry point is :func:`proof_pdf.core.verify_file`; the CLI group lives in
|
|
9
9
|
:mod:`proof_pdf.cli` and is the ``proof-pdf`` console script.
|
|
10
10
|
|
|
11
|
-
Public API is the top-level modules
|
|
12
|
-
:mod:`~proof_pdf.preview`, :mod:`~proof_pdf.texlog
|
|
13
|
-
|
|
11
|
+
Public API is the top-level modules (:mod:`~proof_pdf.core`, :mod:`~proof_pdf.models`, :mod:`~proof_pdf.inventory`,
|
|
12
|
+
:mod:`~proof_pdf.preview`, :mod:`~proof_pdf.texlog`, :mod:`~proof_pdf.config`, :mod:`~proof_pdf.cli`) and the
|
|
13
|
+
:func:`~proof_pdf.config.load_profile` re-export. Everything under ``proof_pdf._internal`` (the checks, the content and
|
|
14
|
+
paint walks, the tool wrappers, the config reader) is private and may change at any time.
|
|
14
15
|
|
|
15
16
|
The package takes PDF paths and nothing else. The finished-page geometry it checks
|
|
16
17
|
against is a parameter (:class:`PageGeometry`) with no default, so a file from a
|
|
17
18
|
printer is held to its own dimensions or the layer is skipped.
|
|
18
19
|
"""
|
|
20
|
+
|
|
21
|
+
from .config import ConfigError, LoadedConfig, load_profile
|
|
22
|
+
|
|
23
|
+
__all__ = ["ConfigError", "LoadedConfig", "load_profile"]
|
|
@@ -228,30 +228,7 @@ def verify_cmd(
|
|
|
228
228
|
typer.echo("no PDFs to verify", err=True)
|
|
229
229
|
raise typer.Exit(1)
|
|
230
230
|
|
|
231
|
-
profile =
|
|
232
|
-
flavour=flavour,
|
|
233
|
-
pdfa=pdfa,
|
|
234
|
-
pdfx=pdfx,
|
|
235
|
-
colour=colour,
|
|
236
|
-
geometry=_page_geometry(trim_mm, bleed_mm, tolerance_mm) if trim_mm else None,
|
|
237
|
-
max_tac=max_tac,
|
|
238
|
-
small_text_pt=small_text_pt,
|
|
239
|
-
min_dpi=min_dpi,
|
|
240
|
-
strict_cmyk=strict_cmyk,
|
|
241
|
-
k_only_text=k_only_text,
|
|
242
|
-
missing_glyphs=missing_glyphs,
|
|
243
|
-
min_text_pt=min_text_pt,
|
|
244
|
-
min_stroke_pt=min_stroke_pt,
|
|
245
|
-
safe_mm=safe_mm,
|
|
246
|
-
min_delta_l=min_delta_l,
|
|
247
|
-
proof_profile=proof_profile,
|
|
248
|
-
fonts=fonts,
|
|
249
|
-
allow_spots=tuple(allow_spot) if allow_spot else None,
|
|
250
|
-
output_condition=output_condition,
|
|
251
|
-
require_bleed=require_bleed,
|
|
252
|
-
uniform_pages=uniform_pages,
|
|
253
|
-
pages_multiple_of=pages_multiple_of,
|
|
254
|
-
)
|
|
231
|
+
profile = build_profile(locals())
|
|
255
232
|
reports = asyncio.run(
|
|
256
233
|
core.verify_files(
|
|
257
234
|
targets,
|
|
@@ -402,6 +379,35 @@ def _parse_exemptions(specs: list[str]) -> dict[str, str]:
|
|
|
402
379
|
return exemptions
|
|
403
380
|
|
|
404
381
|
|
|
382
|
+
def build_profile(options: typing.Mapping[str, typing.Any]) -> Profile:
|
|
383
|
+
"""The :class:`Profile` the ``verify`` options (by parameter name) describe; also used by ``load_profile``."""
|
|
384
|
+
o = options
|
|
385
|
+
return Profile(
|
|
386
|
+
flavour=o["flavour"],
|
|
387
|
+
pdfa=o["pdfa"],
|
|
388
|
+
pdfx=o["pdfx"],
|
|
389
|
+
colour=o["colour"],
|
|
390
|
+
geometry=_page_geometry(o["trim_mm"], o["bleed_mm"], o["tolerance_mm"]) if o["trim_mm"] else None,
|
|
391
|
+
max_tac=o["max_tac"],
|
|
392
|
+
small_text_pt=o["small_text_pt"],
|
|
393
|
+
min_dpi=o["min_dpi"],
|
|
394
|
+
strict_cmyk=o["strict_cmyk"],
|
|
395
|
+
k_only_text=o["k_only_text"],
|
|
396
|
+
missing_glyphs=o["missing_glyphs"],
|
|
397
|
+
min_text_pt=o["min_text_pt"],
|
|
398
|
+
min_stroke_pt=o["min_stroke_pt"],
|
|
399
|
+
safe_mm=o["safe_mm"],
|
|
400
|
+
min_delta_l=o["min_delta_l"],
|
|
401
|
+
proof_profile=o["proof_profile"],
|
|
402
|
+
fonts=o["fonts"],
|
|
403
|
+
allow_spots=tuple(o["allow_spot"]) if o["allow_spot"] else None,
|
|
404
|
+
output_condition=o["output_condition"],
|
|
405
|
+
require_bleed=o["require_bleed"],
|
|
406
|
+
uniform_pages=o["uniform_pages"],
|
|
407
|
+
pages_multiple_of=o["pages_multiple_of"],
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
405
411
|
def _page_geometry(trim_mm: str, bleed_mm: float, tolerance_mm: float) -> PageGeometry:
|
|
406
412
|
"""Parse ``--trim-mm WxH`` into a :class:`PageGeometry`."""
|
|
407
413
|
parts = trim_mm.lower().replace("*", "x").split("x")
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Read a ``proof-pdf.toml`` the way the CLI does, for library callers.
|
|
2
|
+
|
|
3
|
+
One file can then carry a printer's numbers for both ``proof-pdf verify`` and Python code that calls
|
|
4
|
+
:func:`proof_pdf.core.verify_files`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
import os
|
|
9
|
+
import pathlib
|
|
10
|
+
import typing
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
import typer.main
|
|
14
|
+
|
|
15
|
+
from ._internal import config as _config
|
|
16
|
+
from ._internal.config import ConfigError
|
|
17
|
+
from .models import Profile
|
|
18
|
+
|
|
19
|
+
__all__ = ["ConfigError", "LoadedConfig", "load_profile"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclasses.dataclass(frozen=True)
|
|
23
|
+
class LoadedConfig:
|
|
24
|
+
"""What ``verify`` would run with: the checks and the file selection, from the file plus the option defaults."""
|
|
25
|
+
|
|
26
|
+
profile: Profile
|
|
27
|
+
exclude: tuple[str, ...] # globs for :func:`proof_pdf.core.expand_targets`
|
|
28
|
+
exempt: dict[str, str] # glob -> reason, for ``verify_files(exemptions=...)``
|
|
29
|
+
jobs: int
|
|
30
|
+
timeout: float
|
|
31
|
+
verapdf_bin: str
|
|
32
|
+
gs_bin: str
|
|
33
|
+
path: pathlib.Path | None # the file that was read; None when none was found
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_profile(
|
|
37
|
+
path: pathlib.Path | None = None, *, profile: str | None = None, command: str = "verify"
|
|
38
|
+
) -> LoadedConfig:
|
|
39
|
+
"""The settings the CLI's *command* would use, read from the config file.
|
|
40
|
+
|
|
41
|
+
*path* defaults to ``PROOF_PDF_CONFIG``, else the nearest ``proof-pdf.toml`` (or ``[tool.proof-pdf]`` in
|
|
42
|
+
``pyproject.toml``) from the working directory; *profile* defaults to ``PROOF_PDF_PROFILE``. With no file, every
|
|
43
|
+
option has its CLI default. Raises :class:`ConfigError` for an unreadable file, an unknown key or table, a profile
|
|
44
|
+
the file lacks, or a value the CLI would reject.
|
|
45
|
+
"""
|
|
46
|
+
from . import cli # deferred: the CLI framework is only loaded when a caller wants its behaviour
|
|
47
|
+
|
|
48
|
+
if command != "verify":
|
|
49
|
+
raise ConfigError(f"only the verify command makes a profile, not {command!r}")
|
|
50
|
+
path = path or (pathlib.Path(env) if (env := os.environ.get("PROOF_PDF_CONFIG")) else None)
|
|
51
|
+
profile = profile or os.environ.get("PROOF_PDF_PROFILE") or None
|
|
52
|
+
path = path or _config.find_config(pathlib.Path.cwd())
|
|
53
|
+
if path is None and profile:
|
|
54
|
+
raise ConfigError("a profile needs a config file, and none was found")
|
|
55
|
+
commands = _click_commands()
|
|
56
|
+
defaults = _config.default_map(path, profile, commands) if path else {}
|
|
57
|
+
try:
|
|
58
|
+
with commands[command].make_context(command, [], default_map=defaults.get(command, {})) as ctx:
|
|
59
|
+
values = ctx.params
|
|
60
|
+
built = cli.build_profile(values)
|
|
61
|
+
exempt = cli._parse_exemptions(values["exempt"] or [])
|
|
62
|
+
except typer.BadParameter as exc: # a bad value is the file's fault, not a CLI usage error
|
|
63
|
+
raise ConfigError(f"{path or 'defaults'}: {exc.format_message()}") from exc
|
|
64
|
+
return LoadedConfig(
|
|
65
|
+
profile=built,
|
|
66
|
+
exclude=tuple(values["exclude"] or ()),
|
|
67
|
+
exempt=exempt,
|
|
68
|
+
jobs=values["jobs"],
|
|
69
|
+
timeout=values["timeout"],
|
|
70
|
+
verapdf_bin=values["verapdf_bin"],
|
|
71
|
+
gs_bin=values["gs_bin"],
|
|
72
|
+
path=path,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _click_commands() -> dict[str, typing.Any]:
|
|
77
|
+
from . import cli
|
|
78
|
+
|
|
79
|
+
return dict(getattr(typer.main.get_command(cli.pdf_app), "commands", {}))
|
|
@@ -56,6 +56,16 @@ async def verify_file(
|
|
|
56
56
|
return report
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def missing_tools(profile: Profile, *, verapdf_bin: str = "verapdf", gs_bin: str = "gs") -> list[str]:
|
|
60
|
+
"""The external tools *profile* needs that cannot be found, by the executable names given (empty if all are there).
|
|
61
|
+
|
|
62
|
+
veraPDF is needed for the PDF/A layer and Ghostscript for the colour layer (total ink). :func:`verify_file` reports
|
|
63
|
+
a missing tool as a failing finding; this lets a caller stop before a long run with one message of its own.
|
|
64
|
+
"""
|
|
65
|
+
needed = ((verapdf_bin, profile.pdfa, verapdf.available), (gs_bin, profile.colour, colour.gs_available))
|
|
66
|
+
return [binary for binary, wanted, found in needed if wanted and not found(binary)]
|
|
67
|
+
|
|
68
|
+
|
|
59
69
|
def exemption(path: pathlib.PurePath, exemptions: collections.abc.Mapping[str, str]) -> str | None:
|
|
60
70
|
"""The reason *path* was declared exempt, or ``None``.
|
|
61
71
|
|
|
@@ -259,3 +259,18 @@ class TestFileReport:
|
|
|
259
259
|
)
|
|
260
260
|
def test_finding_label(self, page, expected):
|
|
261
261
|
assert Finding("x", Severity.ERROR, "hello", page).label() == expected
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class TestMissingTools:
|
|
265
|
+
def test_names_only_what_the_profile_needs_and_cannot_find(self, mocker):
|
|
266
|
+
mocker.patch.object(verapdf, "available", return_value=False)
|
|
267
|
+
mocker.patch.object(colour, "gs_available", return_value=False)
|
|
268
|
+
assert core.missing_tools(Profile(), verapdf_bin="vera", gs_bin="ghost") == ["vera", "ghost"]
|
|
269
|
+
assert core.missing_tools(Profile(pdfa=False)) == ["gs"]
|
|
270
|
+
assert core.missing_tools(Profile(colour=False)) == ["verapdf"]
|
|
271
|
+
assert core.missing_tools(Profile(pdfa=False, colour=False)) == []
|
|
272
|
+
|
|
273
|
+
def test_a_tool_that_is_there_is_not_reported(self, mocker):
|
|
274
|
+
mocker.patch.object(verapdf, "available", return_value=True)
|
|
275
|
+
mocker.patch.object(colour, "gs_available", return_value=True)
|
|
276
|
+
assert core.missing_tools(Profile()) == []
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""``proof_pdf.load_profile``: the CLI's config reading, for library callers."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
import proof_pdf
|
|
6
|
+
from proof_pdf.models import PageGeometry, Profile
|
|
7
|
+
|
|
8
|
+
CARD = """
|
|
9
|
+
[verify]
|
|
10
|
+
trim-mm = "105x148"
|
|
11
|
+
bleed-mm = 3
|
|
12
|
+
exclude = ["res/**", "_views/**", "*_preview.pdf"]
|
|
13
|
+
safe-mm = 3
|
|
14
|
+
require-bleed = true
|
|
15
|
+
|
|
16
|
+
[verify.exempt]
|
|
17
|
+
"manual.pdf" = "internal"
|
|
18
|
+
|
|
19
|
+
[profiles.deluxe.verify]
|
|
20
|
+
bleed-mm = 5
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def card(tmp_path, monkeypatch):
|
|
26
|
+
monkeypatch.delenv("PROOF_PDF_CONFIG", raising=False)
|
|
27
|
+
monkeypatch.delenv("PROOF_PDF_PROFILE", raising=False)
|
|
28
|
+
path = tmp_path / "proof-pdf.toml"
|
|
29
|
+
path.write_text(CARD)
|
|
30
|
+
return path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_the_file_gives_the_profile_the_globs_and_the_exemptions(card):
|
|
34
|
+
loaded = proof_pdf.load_profile(card)
|
|
35
|
+
assert loaded.profile.geometry == PageGeometry(trim_w_mm=105, trim_h_mm=148, bleed_mm=3)
|
|
36
|
+
assert loaded.profile.safe_mm == 3 and loaded.profile.require_bleed is True
|
|
37
|
+
assert loaded.exclude == ("res/**", "_views/**", "*_preview.pdf")
|
|
38
|
+
assert loaded.exempt == {"manual.pdf": "internal"} and loaded.path == card
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_a_named_profile_overrides_the_base(card):
|
|
42
|
+
assert proof_pdf.load_profile(card, profile="deluxe").profile.geometry.bleed_mm == 5 # type: ignore[union-attr]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_the_nearest_file_is_found_from_the_working_directory(card, monkeypatch):
|
|
46
|
+
monkeypatch.chdir(card.parent)
|
|
47
|
+
assert proof_pdf.load_profile().path == card
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_the_environment_names_the_file_and_profile(card, monkeypatch, tmp_path):
|
|
51
|
+
monkeypatch.chdir(tmp_path / "..")
|
|
52
|
+
monkeypatch.setenv("PROOF_PDF_CONFIG", str(card))
|
|
53
|
+
monkeypatch.setenv("PROOF_PDF_PROFILE", "deluxe")
|
|
54
|
+
assert proof_pdf.load_profile().profile.geometry.bleed_mm == 5 # type: ignore[union-attr]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_no_file_means_the_cli_defaults(tmp_path, monkeypatch):
|
|
58
|
+
monkeypatch.delenv("PROOF_PDF_CONFIG", raising=False)
|
|
59
|
+
monkeypatch.chdir(tmp_path)
|
|
60
|
+
loaded = proof_pdf.load_profile()
|
|
61
|
+
assert loaded.profile == Profile() and loaded.path is None and loaded.exclude == () and loaded.exempt == {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
"text",
|
|
66
|
+
[
|
|
67
|
+
pytest.param("[verify]\nnot-an-option = 1\n", id="unknown-key"),
|
|
68
|
+
pytest.param('[verify]\ntrim-mm = "wide"\n', id="bad-trim"),
|
|
69
|
+
pytest.param('[verify]\nmin-dpi = "high"\n', id="bad-type"),
|
|
70
|
+
pytest.param("[nope]\nx = 1\n", id="unknown-table"),
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
def test_a_file_the_cli_would_reject_is_a_config_error(tmp_path, monkeypatch, text):
|
|
74
|
+
monkeypatch.delenv("PROOF_PDF_CONFIG", raising=False)
|
|
75
|
+
path = tmp_path / "proof-pdf.toml"
|
|
76
|
+
path.write_text(text)
|
|
77
|
+
with pytest.raises(proof_pdf.ConfigError):
|
|
78
|
+
proof_pdf.load_profile(path)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_a_profile_without_a_file_is_a_config_error(tmp_path, monkeypatch):
|
|
82
|
+
monkeypatch.delenv("PROOF_PDF_CONFIG", raising=False)
|
|
83
|
+
monkeypatch.chdir(tmp_path)
|
|
84
|
+
with pytest.raises(proof_pdf.ConfigError):
|
|
85
|
+
proof_pdf.load_profile(profile="deluxe")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_only_verify_makes_a_profile():
|
|
89
|
+
with pytest.raises(proof_pdf.ConfigError):
|
|
90
|
+
proof_pdf.load_profile(command="inspect")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|