pdflinkcheck 1.1.7__py3-none-any.whl → 1.1.72__py3-none-any.whl
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.
- pdflinkcheck/__init__.py +69 -0
- pdflinkcheck/analyze_pymupdf.py +338 -0
- pdflinkcheck/analyze_pypdf.py +184 -0
- pdflinkcheck/analyze_pypdf_v2.py +218 -0
- pdflinkcheck/cli.py +303 -27
- pdflinkcheck/data/LICENSE +661 -0
- pdflinkcheck/data/README.md +278 -0
- pdflinkcheck/data/pyproject.toml +98 -0
- pdflinkcheck/datacopy.py +60 -0
- pdflinkcheck/dev.py +109 -0
- pdflinkcheck/gui.py +477 -52
- pdflinkcheck/io.py +213 -0
- pdflinkcheck/report.py +280 -0
- pdflinkcheck/stdlib_server.py +176 -0
- pdflinkcheck/validate.py +380 -0
- pdflinkcheck/version_info.py +83 -0
- pdflinkcheck-1.1.72.dist-info/METADATA +322 -0
- pdflinkcheck-1.1.72.dist-info/RECORD +21 -0
- pdflinkcheck-1.1.72.dist-info/WHEEL +4 -0
- {pdflinkcheck-1.1.7.dist-info → pdflinkcheck-1.1.72.dist-info}/entry_points.txt +1 -1
- pdflinkcheck-1.1.72.dist-info/licenses/LICENSE +661 -0
- pdflinkcheck/analyze.py +0 -330
- pdflinkcheck/remnants.py +0 -142
- pdflinkcheck-1.1.7.dist-info/METADATA +0 -109
- pdflinkcheck-1.1.7.dist-info/RECORD +0 -10
- pdflinkcheck-1.1.7.dist-info/WHEEL +0 -5
- pdflinkcheck-1.1.7.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# pdflinkcheck
|
|
2
|
+
|
|
3
|
+
A purpose-built tool for comprehensive analysis of hyperlinks and GoTo links within PDF documents. Users may leverage either the PyMuPDF or the pypdf library. Use the CLI or the GUI.
|
|
4
|
+
|
|
5
|
+
-----
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
-----
|
|
10
|
+
|
|
11
|
+
## 📥 Access and Installation
|
|
12
|
+
|
|
13
|
+
The recommended way to use `pdflinkcheck` is to either install the CLI with `pipx` or to download the appropriate latest binary for your system from [Releases](https://github.com/City-of-Memphis-Wastewater/pdflinkcheck/releases/).
|
|
14
|
+
|
|
15
|
+
### 🚀 Release Artifact Files (EXE, PYZ, ELF)
|
|
16
|
+
|
|
17
|
+
For the most user-typical experience, download the single-file binary matching your OS.
|
|
18
|
+
|
|
19
|
+
| **File Type** | **Primary Use Case** | **Recommended Launch Method** |
|
|
20
|
+
| :--- | :--- | :--- |
|
|
21
|
+
| **Executable (.exe, .elf)** | **GUI** | Double-click the file. |
|
|
22
|
+
| **PYZ (Python Zip App)** | **CLI** or **GUI** | Run using your system's `python` command: `python pdflinkcheck-VERSION.pyz --help` |
|
|
23
|
+
|
|
24
|
+
### Installation via pipx
|
|
25
|
+
|
|
26
|
+
For an isolated environment where you can access `pdflinkcheck` from any terminal:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Ensure you have pipx installed first (if not, run: pip install pipx)
|
|
30
|
+
pipx install pdflinkcheck[full]
|
|
31
|
+
|
|
32
|
+
# On Termux
|
|
33
|
+
pipx install pdflinkcheck
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
-----
|
|
38
|
+
|
|
39
|
+
## 💻 Graphical User Interface (GUI)
|
|
40
|
+
|
|
41
|
+
The tool can be run as simple cross-platform graphical interface (Tkinter).
|
|
42
|
+
|
|
43
|
+
### Launching the GUI
|
|
44
|
+
|
|
45
|
+
There are three ways to launch the GUI interface:
|
|
46
|
+
|
|
47
|
+
1. **Implicit Launch:** Run the main command with no arguments, subcommands, or flags (`pdflinkcheck`).
|
|
48
|
+
2. **Explicit Command:** Use the dedicated GUI subcommand (`pdflinkcheck gui`).
|
|
49
|
+
3. **Binary Double-Click:**
|
|
50
|
+
* **Windows:** Double-click the `pdflinkcheck-VERSION-gui.bat` file.
|
|
51
|
+
* **macOS/Linux:** Double-click the downloaded `.pyz` or `.elf` file.
|
|
52
|
+
|
|
53
|
+
### Planned GUI Updates
|
|
54
|
+
|
|
55
|
+
We are actively working on the following enhancements:
|
|
56
|
+
|
|
57
|
+
* **Report Export:** Functionality to export the full analysis report to a plain text file.
|
|
58
|
+
* **License Visibility:** A dedicated "License Info" button within the GUI to display the terms of the AGPLv3+ license.
|
|
59
|
+
|
|
60
|
+
-----
|
|
61
|
+
|
|
62
|
+
## 🚀 CLI Usage
|
|
63
|
+
|
|
64
|
+
The core functionality is accessed via the `analyze` command.
|
|
65
|
+
|
|
66
|
+
`DEV_TYPER_HELP_TREE=1 pdflinkcheck help-tree`:
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
`pdflinkcheck --help`:
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
### Available Commands
|
|
74
|
+
|
|
75
|
+
|**Command**|**Description**|
|
|
76
|
+
|---|---|
|
|
77
|
+
|`pdflinkcheck analyze`|Analyzes a PDF file for links |
|
|
78
|
+
|`pdflinkcheck gui`|Explicitly launch the Graphical User Interface.|
|
|
79
|
+
|`pdflinkcheck docs`|Access documentation, including the README and AGPLv3+ license.|
|
|
80
|
+
|
|
81
|
+
### `analyze` Command Options
|
|
82
|
+
|
|
83
|
+
|**Option**|**Description**|**Default**|
|
|
84
|
+
|---|---|---|
|
|
85
|
+
|`<PDF_PATH>`|**Required.** The path to the PDF file to analyze.|N/A|
|
|
86
|
+
|`--pdf-library / -p`|Select engine: `pymupdf` or `pypdf`.|`pypdf`|
|
|
87
|
+
|`--export-format / -e`|Export to `JSON`, `TXT`, or `None` to suppress file output.|`JSON`|
|
|
88
|
+
|`--max-links / -m`|Maximum links to display per section. Use `0` for all.|`0`|
|
|
89
|
+
|
|
90
|
+
### `gui` Command Options
|
|
91
|
+
|
|
92
|
+
| **Option** | **Description** | **Default** |
|
|
93
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------- | -------------- |
|
|
94
|
+
| `--auto-close INTEGER` | **(For testing/automation only).** Delay in milliseconds after which the GUI window will automatically close. | `0` (Disabled) |
|
|
95
|
+
|
|
96
|
+
#### Example Runs
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Analyze a document, show all links, and save the report as JSON and TXT
|
|
100
|
+
pdflinkcheck analyze "TE Maxson WWTF O&M Manual.pdf" --export-format JSON,TXT
|
|
101
|
+
|
|
102
|
+
# Analyze a document but keep the print block short, showing only the first 10 links for each type
|
|
103
|
+
pdflinkcheck analyze "TE Maxson WWTF O&M Manual.pdf" --max-links 10
|
|
104
|
+
|
|
105
|
+
# Show the GUI for only a moment, like in a build check
|
|
106
|
+
pdflinkcheck gui --auto-close 3000
|
|
107
|
+
|
|
108
|
+
# Show both the LICENSE and README.md docs
|
|
109
|
+
pdflinkcheck docs --license --readme
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
-----
|
|
113
|
+
|
|
114
|
+
## 📦 Library Access (Advanced)
|
|
115
|
+
|
|
116
|
+
For developers importing `pdflinkcheck` into other Python projects, the core analysis functions are exposed directly in the root namespace:
|
|
117
|
+
|
|
118
|
+
|**Function**|**Description**|
|
|
119
|
+
|---|---|
|
|
120
|
+
|`run_report()`|**(Primary function)** Performs the full analysis, prints to console, and handles file export.|
|
|
121
|
+
|`extract_links_pynupdf()`|Function to retrieve all explicit links (URIs, GoTo, etc.) from a PDF path.|
|
|
122
|
+
|`extract_toc_pymupdf()`|Function to extract the PDF's internal Table of Contents (bookmarks/outline).|
|
|
123
|
+
|`extract_links_pynupdf()`|Function to retrieve all explicit links (URIs, GoTo, etc.) from a PDF path, using the pypdf library.|
|
|
124
|
+
|`extract_toc_pymupdf()`|Function to extract the PDF's internal Table of Contents (bookmarks/outline), using the pypdf library.|
|
|
125
|
+
|
|
126
|
+
Exanple:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from pdflinkcheck.report import run_report
|
|
130
|
+
from pdflinkcheck.analysis_pymupdf import extract_links_pymupdf, extract_toc_pymupdf 130 from pdflinkcheck.analysis_pymupdf import extract_links_pynupdf, extract_toc_pymupdf
|
|
131
|
+
from pdflinkcheck.analysis_pypdf import extract_links_pypdf, extract_toc_pypdf
|
|
132
|
+
|
|
133
|
+
file = "document1.pdf"
|
|
134
|
+
report_data = run_report(file)
|
|
135
|
+
links_pymupdf = extract_links_pymupdf(file)
|
|
136
|
+
links_pypdf = extract_links_pypdf(file)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
-----
|
|
140
|
+
|
|
141
|
+
## ✨ Features
|
|
142
|
+
|
|
143
|
+
* **Active Link Extraction:** Identifies and categorizes all programmed links (External URIs, Internal GoTo/Destinations, Remote Jumps).
|
|
144
|
+
* **Anchor Text Retrieval:** Extracts the visible text corresponding to each link's bounding box.
|
|
145
|
+
* **Structural TOC:** Extracts the PDF's internal Table of Contents (bookmarks/outline).
|
|
146
|
+
|
|
147
|
+
-----
|
|
148
|
+
|
|
149
|
+
## 🥚 Optional REPL‑Friendly GUI Access (Easter Egg)
|
|
150
|
+
|
|
151
|
+
For users who prefer exploring tools interactively—especially those coming from MATLAB or other REPL‑first environments—`pdflinkcheck` includes an optional Easter egg that exposes the GUI launcher directly in the library namespace.
|
|
152
|
+
|
|
153
|
+
This feature is **disabled by default** and has **no effect on normal imports**.
|
|
154
|
+
|
|
155
|
+
### Enabling the Easter Egg
|
|
156
|
+
|
|
157
|
+
Set the environment variable before importing the library:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
import os
|
|
161
|
+
os.environ["PDFLINKCHECK_GUI_EASTEREGG"] = "true"
|
|
162
|
+
|
|
163
|
+
import pdflinkcheck
|
|
164
|
+
pdflinkcheck.start_gui()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Accepted values include: `true`, `1`, `yes`, `on` (case‑insensitive).
|
|
168
|
+
|
|
169
|
+
### Purpose
|
|
170
|
+
|
|
171
|
+
This opt‑in behavior is designed to make the library feel welcoming to beginners who are experimenting in a Python REPL for the first time. When enabled, the `start_gui()` function becomes available at the top level:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
pdflinkcheck.start_gui()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If the `PDFLINKCHECK_GUI_EASTEREGG` environment variable is not set—or if GUI support is unavailable—`pdflinkcheck` behaves as a normal library with no GUI functions exposed.
|
|
178
|
+
|
|
179
|
+
### Another Easter Egg
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
DEV_TYPER_HELP_TREE=1 pdflinkcheck help-tree
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This `help-tree` feature has not yet been submitted for inclusion into Typer.
|
|
186
|
+
|
|
187
|
+
-----
|
|
188
|
+
|
|
189
|
+
## ⚠️ Compatibility Notes
|
|
190
|
+
|
|
191
|
+
#### Termux Compatibility as a Key Goal
|
|
192
|
+
A key goal of City-of-Memphis-Wastewater is to release all software as Termux-compatible.
|
|
193
|
+
|
|
194
|
+
Termux compatibility is important in the modern age as Android devices are common among technicians, field engineers, and maintenace staff.
|
|
195
|
+
Android is the most common operating system in the Global South.
|
|
196
|
+
We aim to produce stable software that can do the most possible good.
|
|
197
|
+
|
|
198
|
+
While using `PyMuPDF` in Python dependency resolution on Termux simply isn't possible, we are proud to have achieved a work-around by implementing a parallel solution in `pypdf`!
|
|
199
|
+
Now, there is PDF Engine selection in both the CLI and the GUI.
|
|
200
|
+
`pypdf` is the default in pdflinkcheck.report.run_report(); PyMuPDF can be explicitly requested in the CLI and is the default in the TKinter GUI.
|
|
201
|
+
|
|
202
|
+
Now that `pdflinkcheck` can run on Termux, we may find a work-around and be able to drop the PyMuPDF dependency.
|
|
203
|
+
- Build `pypdf`-only artifacts, to reduce size.
|
|
204
|
+
- Build a web-stack GUI as an alternative to the Tkinter GUI, to be compatible with Termux.
|
|
205
|
+
|
|
206
|
+
Because it works, we plan to keep the `PyMuPDF` portion of the codebase.
|
|
207
|
+
|
|
208
|
+
### Document Compatibility:
|
|
209
|
+
Not all PDF files can be processed successfully. This tool is designed primarily for digitally generated (vector-based) PDFs.
|
|
210
|
+
|
|
211
|
+
Processing may fail or yield incomplete results for:
|
|
212
|
+
* **Scanned PDFs** (images of text) that lack an accessible text layer.
|
|
213
|
+
* **Encrypted or Password-Protected** documents.
|
|
214
|
+
* **Malformed or non-standard** PDF files.
|
|
215
|
+
|
|
216
|
+
-----
|
|
217
|
+
|
|
218
|
+
## PDF Library Selection
|
|
219
|
+
At long last, `PyMuPDF` is an optional dependency. The default is `pypdf`. All testing has shown identical performance, though the `analyze_pymupdf.py` is faster and more direct and robust than `analyze_pypdf.py`, which requires a lot of intentional parsing.
|
|
220
|
+
|
|
221
|
+
Binaries and artifacts are expected to contain PyMuPDF, unless they are build on Android. The GUI and CLI interfaces both allow selection of the library; if PyMuPDF is selected but is not available, the user will be warned.
|
|
222
|
+
|
|
223
|
+
To install the complete version use one of these options:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
pip install "pdflinkcheck[full]"
|
|
227
|
+
pipx install "pdflinkcheck[full]"
|
|
228
|
+
uv tool install "pdflinkcheck[full]"
|
|
229
|
+
uv add "pdflinkcheck[full]"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
-----
|
|
233
|
+
|
|
234
|
+
## Run from Source (Developers)
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
git clone http://github.com/city-of-memphis-wastewater/pdflinkcheck.git
|
|
238
|
+
cd pdflinkcheck
|
|
239
|
+
|
|
240
|
+
# To include the PyMuPDF dependency in the installation:
|
|
241
|
+
uv sync --extras full
|
|
242
|
+
|
|
243
|
+
# On Termux, to not include PyMuPDF:
|
|
244
|
+
uv sync
|
|
245
|
+
|
|
246
|
+
# To include developer depedecies:
|
|
247
|
+
uv sync --all-extras --group dev
|
|
248
|
+
|
|
249
|
+
# Run the CLI
|
|
250
|
+
uv run python src/pdflinkcheck/cli.py --help
|
|
251
|
+
|
|
252
|
+
# Run a basic webapp and Termux-facing browser-based interface
|
|
253
|
+
uv run python -m pdflinkcheck.stdlib_server
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
-----
|
|
257
|
+
|
|
258
|
+
## 📜 License Implications (AGPLv3+)
|
|
259
|
+
|
|
260
|
+
**`pdflinkcheck` is licensed under the `GNU Affero General Public License` version 3 or later (`AGPLv3+`).**
|
|
261
|
+
|
|
262
|
+
The `AGPL3+` is required for portions of this codebase because `pdflinkcheck` uses `PyMuPDF`, which is licensed under the `AGPL3`.
|
|
263
|
+
|
|
264
|
+
To stay in compliance, the AGPL3 license text is readily available in the CLI and the GUI, and it is included in the build artifacts.
|
|
265
|
+
The `AGPL3` appears as the primary license file in the source code. While this infers that the entire project is AGPL3-licensed, this is not true - portions of the codebase are MIT-licensed.
|
|
266
|
+
|
|
267
|
+
This license has significant implications for **distribution and network use**, particularly for organizations:
|
|
268
|
+
|
|
269
|
+
* **Source Code Provision:** If you distribute this tool (modified or unmodified) to anyone, you **must** provide the full source code under the same license.
|
|
270
|
+
* **Network Interaction (Affero Clause):** If you modify this tool and make the modified version available to users over a computer network (e.g., as a web service or backend), you **must** also offer the source code to those network users.
|
|
271
|
+
|
|
272
|
+
> **Before deploying or modifying this tool for organizational use, especially for internal web services or distribution, please ensure compliance with the AGPLv3+ terms.**
|
|
273
|
+
|
|
274
|
+
Links:
|
|
275
|
+
- Source code: https://github.com/City-of-Memphis-Wastewater/pdflinkcheck/
|
|
276
|
+
- Official AGPLv3 Text (FSF): https://www.gnu.org/licenses/agpl-3.0.html
|
|
277
|
+
|
|
278
|
+
Copyright © 2025 George Clayton Bennett
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pdflinkcheck"
|
|
3
|
+
version = "1.1.72"
|
|
4
|
+
description = "A purpose-built PDF link analysis and reporting tool with GUI and CLI."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pyhabitat>=1.0.53",
|
|
9
|
+
"pypdf>=6.4.2",
|
|
10
|
+
"rich>=14.2.0",
|
|
11
|
+
"typer>=0.20.0",
|
|
12
|
+
]
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
classifiers=[
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Intended Audience :: End Users/Desktop",
|
|
25
|
+
"Intended Audience :: Developers", # library and documentation
|
|
26
|
+
"Intended Audience :: Science/Research", # Technical manuals, engineering
|
|
27
|
+
"Intended Audience :: Other Audience", # Government/engineering docs
|
|
28
|
+
"Topic :: File Formats",
|
|
29
|
+
"Topic :: Office/Business",
|
|
30
|
+
"Topic :: Text Processing :: General",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
32
|
+
"Environment :: Console",
|
|
33
|
+
"Environment :: MacOS X",
|
|
34
|
+
"Environment :: Win32 (MS Windows)",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
"Development Status :: 4 - Beta",
|
|
37
|
+
#"Development Status :: 5 - Production/Stable",
|
|
38
|
+
]
|
|
39
|
+
authors = [
|
|
40
|
+
{ name = "George Clayton Bennett", email = "george.bennett@memphistn.gov" },
|
|
41
|
+
]
|
|
42
|
+
maintainers = [
|
|
43
|
+
{ name = "George Clayton Bennett", email = "george.bennett@memphistn.gov" },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/city-of-memphis-wastewater/pdflinkcheck"
|
|
48
|
+
Repository = "https://github.com/city-of-memphis-wastewater/pdflinkcheck"
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
pdflinkcheck = "pdflinkcheck.cli:app"
|
|
52
|
+
|
|
53
|
+
[project.optional-dependencies]
|
|
54
|
+
# This allows users to do: pip install pdflinkcheck[full]
|
|
55
|
+
full = [
|
|
56
|
+
#"pymupdf>=1.26.7 ; platform_system == 'Linux' and platform_machine != 'aarch64'" # to avoid on termux
|
|
57
|
+
"pymupdf>=1.26.7" # let them try
|
|
58
|
+
]
|
|
59
|
+
gui = [
|
|
60
|
+
"sv-ttk>=2.6.1",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[dependency-groups]
|
|
64
|
+
dev = [
|
|
65
|
+
"build>=1.3.0",
|
|
66
|
+
"pyinstaller>=6.17.0",
|
|
67
|
+
"shiv>=1.0.8",
|
|
68
|
+
"ruff>=0.7.0 ; platform_system == 'Linux' and platform_machine != 'aarch64'", # to avoid on termux
|
|
69
|
+
"pytest>=8.0.0",
|
|
70
|
+
"pytest-cov>=4.1.0",
|
|
71
|
+
"sv-ttk>=2.6.1",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[build-system]
|
|
75
|
+
requires = ["uv_build"]
|
|
76
|
+
build-backend = "uv_build"
|
|
77
|
+
|
|
78
|
+
#[tool.hatch.build.targets.wheel]
|
|
79
|
+
#packages = ["src/pdflinkcheck"]
|
|
80
|
+
#artifacts = [
|
|
81
|
+
# "src/pdflinkcheck/data/LICENSE",
|
|
82
|
+
# "src/pdflinkcheck/data/README.md"
|
|
83
|
+
# ]
|
|
84
|
+
|
|
85
|
+
#[tool.uv.build-backend.data]
|
|
86
|
+
#data = ["src/pdflinkcheck/data"]
|
|
87
|
+
|
|
88
|
+
#[tool.hatch.build.targets.wheel.force-include]
|
|
89
|
+
#"pyproject.toml" = "pdflinkcheck/data/pyproject.toml"
|
|
90
|
+
|
|
91
|
+
#[tool.hatch.build.targets.sdist]
|
|
92
|
+
#include = [
|
|
93
|
+
# "pyproject.toml",
|
|
94
|
+
# "src/pdflinkcheck/data/LICENSE",
|
|
95
|
+
# "src/pdflinkcheck/data/README.md",
|
|
96
|
+
#]
|
|
97
|
+
|
|
98
|
+
|
pdflinkcheck/datacopy.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# src/pdflinkcheck/datacopy.py
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# --- COPY LICENSE FILE TO PACKAGE DATA ---
|
|
10
|
+
def ensure_package_pyproject(source_root_path: Path, package_data_path: Path):
|
|
11
|
+
"""Copies the root puyproject.toml file into the expected package data path."""
|
|
12
|
+
source = source_root_path / "pyproject.toml"
|
|
13
|
+
destination = package_data_path / "src" / "pdflinkcheck" / "data" / "pyproject.toml"
|
|
14
|
+
|
|
15
|
+
if not source.exists():
|
|
16
|
+
print(f"FATAL: Root pyproject.toml file not found at {source}!", file=sys.stderr)
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
print(f"Ensuring package pyproject.toml is copied to: {destination}")
|
|
20
|
+
destination.parent.mkdir(parents=True, exist_ok=True) # Ensure data dir exists
|
|
21
|
+
shutil.copy2(source, destination) # copy2 preserves metadata
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# --- COPY LICENSE FILE TO PACKAGE DATA ---
|
|
25
|
+
def ensure_package_license(source_root_path: Path, package_data_path: Path):
|
|
26
|
+
"""Copies the root LICENSE file into the expected package data path."""
|
|
27
|
+
source = source_root_path / "LICENSE"
|
|
28
|
+
destination = package_data_path / "src" / "pdflinkcheck" / "data" / "LICENSE"
|
|
29
|
+
|
|
30
|
+
if not source.exists():
|
|
31
|
+
print(f"FATAL: Root license file not found at {source}!", file=sys.stderr)
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
print(f"Ensuring package license is copied to: {destination}")
|
|
35
|
+
destination.parent.mkdir(parents=True, exist_ok=True) # Ensure data dir exists
|
|
36
|
+
shutil.copy2(source, destination) # copy2 preserves metadata
|
|
37
|
+
|
|
38
|
+
# --- COPY README FILE TO PACKAGE DATA ---
|
|
39
|
+
def ensure_package_readme(source_root_path: Path, package_data_path: Path):
|
|
40
|
+
"""Copies the root README.md file into the expected package data path."""
|
|
41
|
+
source = source_root_path / "README.md"
|
|
42
|
+
destination = package_data_path / "src" / "pdflinkcheck" / "data" / "README.md"
|
|
43
|
+
|
|
44
|
+
if not source.exists():
|
|
45
|
+
print(f"FATAL: Root README file not found at {source}!", file=sys.stderr)
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
print(f"Ensuring package README is copied to: {destination}")
|
|
49
|
+
destination.parent.mkdir(parents=True, exist_ok=True) # Ensure data dir exists
|
|
50
|
+
shutil.copy2(source, destination) # copy2 preserves metadata
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ensure_data_files_for_build():
|
|
54
|
+
print(f"PROJECT_ROOT = {PROJECT_ROOT}")
|
|
55
|
+
ensure_package_license(PROJECT_ROOT, PROJECT_ROOT)
|
|
56
|
+
ensure_package_readme(PROJECT_ROOT, PROJECT_ROOT)
|
|
57
|
+
ensure_package_pyproject(PROJECT_ROOT, PROJECT_ROOT)
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
ensure_data_files_for_build()
|
pdflinkcheck/dev.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# pdflinkcheck/dev.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Experiemental developer-facing function(s).
|
|
5
|
+
|
|
6
|
+
help_tree_command() used click and typer internals which might change version to version.
|
|
7
|
+
|
|
8
|
+
This portion of the codebase is MIT licensed. It does not rely on any AGPL-licensed code.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
MIT License
|
|
13
|
+
|
|
14
|
+
Copyright (c) 2025 George Clayton Bennett <george.bennett@memphistn.gov>
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in all
|
|
24
|
+
copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
|
+
SOFTWARE.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import typer
|
|
37
|
+
from rich.tree import Tree
|
|
38
|
+
from rich.panel import Panel
|
|
39
|
+
import click
|
|
40
|
+
|
|
41
|
+
from pdflinkcheck.version_info import get_version_from_pyproject # change to import from pyhabitat
|
|
42
|
+
|
|
43
|
+
def add_typer_help_tree(app,
|
|
44
|
+
console):
|
|
45
|
+
@app.command(name="help-tree",
|
|
46
|
+
#envvar="PDF_ENGINE",
|
|
47
|
+
help="Show all commands and options in a tree structure.")
|
|
48
|
+
def help_tree_command(ctx: typer.Context):
|
|
49
|
+
"""
|
|
50
|
+
Fragile developer-facing function.
|
|
51
|
+
Generates and prints a tree view of the CLI structure (commands and flags).
|
|
52
|
+
"""
|
|
53
|
+
root_app_command = ctx.parent.command
|
|
54
|
+
|
|
55
|
+
# 1. Start the Rich Tree structure
|
|
56
|
+
app_tree = Tree(
|
|
57
|
+
f"[bold blue]{root_app_command.name}[/bold blue] (v{get_version_from_pyproject()})",
|
|
58
|
+
guide_style="cyan"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# 2. Iterate through all subcommands of the main app
|
|
62
|
+
for command_name in sorted(root_app_command.commands.keys()):
|
|
63
|
+
command = root_app_command.commands[command_name]
|
|
64
|
+
|
|
65
|
+
if command.name == "tree-help":
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
help_text = command.help.splitlines()[0].strip() if command.help else "No help available."
|
|
69
|
+
|
|
70
|
+
command_branch = app_tree.add(f"[bold white]{command.name}[/bold white] - [dim]{help_text}[/dim]")
|
|
71
|
+
|
|
72
|
+
# 3. Add Arguments and Options (Flags)
|
|
73
|
+
params_branch = command_branch.add("[yellow]Parameters[/yellow]:")
|
|
74
|
+
|
|
75
|
+
if not command.params:
|
|
76
|
+
params_branch.add("[dim]None[/dim]")
|
|
77
|
+
|
|
78
|
+
for param in command.params:
|
|
79
|
+
# New, safer check: Check if param is an Option by looking for opts attribute
|
|
80
|
+
# and ensuring it has a flag declaration (starts with '-')
|
|
81
|
+
is_option = hasattr(param, 'opts') and param.opts and param.opts[0].startswith('-')
|
|
82
|
+
|
|
83
|
+
if is_option:
|
|
84
|
+
# This is an Option/Flag
|
|
85
|
+
flag_names = " / ".join(param.opts)
|
|
86
|
+
|
|
87
|
+
# Filter out the default Typer/Click flags like --help
|
|
88
|
+
if flag_names in ("-h", "--help"):
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Handling default value safely
|
|
92
|
+
# Check for None explicitly, as well as the Typer/Click internal sentinel value for not provided.
|
|
93
|
+
default_value = getattr(param, 'default', None)
|
|
94
|
+
|
|
95
|
+
# This is the sentinel value used by the Click/Typer internals
|
|
96
|
+
if default_value not in (None, click.core.UNSET):
|
|
97
|
+
default = f"[dim] (default: {default_value})[/dim]"
|
|
98
|
+
else:
|
|
99
|
+
default = ""
|
|
100
|
+
|
|
101
|
+
params_branch.add(f"[green]{flag_names}[/green]: [dim]{param.help}[/dim]{default}")
|
|
102
|
+
else:
|
|
103
|
+
# This is an Argument (Positional)
|
|
104
|
+
# Arguments have a single name property, not an opts list.
|
|
105
|
+
arg_name = param.human_readable_name.upper()
|
|
106
|
+
params_branch.add(f"[magenta]ARG: {arg_name}[/magenta]: [dim]{param.help}[/dim]")
|
|
107
|
+
|
|
108
|
+
# 4. Print the final Panel containing the tree
|
|
109
|
+
console.print(Panel(app_tree, title=f"[bold]{root_app_command.name} CLI Tree Help[/bold]", border_style="blue"))
|