pdfh 4.0.1 → 4.0.3
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.
- checksums.yaml +4 -4
- data/.pre-commit-config.yaml +0 -6
- data/AGENTS.md +27 -6
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +38 -37
- data/lib/pdfh/services/document_manager.rb +10 -9
- data/lib/pdfh/utils/file_info.rb +6 -2
- data/lib/pdfh/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5ef9e4932b73970339e621800eaadd8de7c0bbc07c4536ea4f360fc5227c27a
|
|
4
|
+
data.tar.gz: 9b02deb23b1522d0debb7a9b480b82b7e69c3bedf286743cd3e7a4c065bc6e97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f694edb6bbe90f5cccb66d94bdf901983b9824f9de71e444065fa98aab75da4ab951965aed0b18de2246c60768e223035294b34d2c3459c251a5a7df2f13678
|
|
7
|
+
data.tar.gz: e2253c53cf4936b9422465ba0bbdf5a7a59b0fc8c0ec8b4bbb63e0bac5d613d80d8ccb490c218d6f7efc09eb16b077e9d13e65c958e6c3ce099cd0b905bf2280
|
data/.pre-commit-config.yaml
CHANGED
data/AGENTS.md
CHANGED
|
@@ -43,10 +43,18 @@ Useful `mise` tasks (`mise.toml`):
|
|
|
43
43
|
- `mise run test` (runs `bundle exec rspec --order random --format documentation`)
|
|
44
44
|
- `mise run check` (runs lint + test)
|
|
45
45
|
- `mise run bump[major|minor|tiny]`
|
|
46
|
-
- `mise run pre` (runs `pre-commit run --all-files`)
|
|
46
|
+
- `mise run pre` (runs `pre-commit run --all-files`; also runs `gitleaks` and `codespell`, which can block a commit independently of RuboCop/tests)
|
|
47
47
|
- `mise run update` (runs `bundle update`)
|
|
48
48
|
- `mise run release` (builds gem + pushes to RubyGems)
|
|
49
49
|
|
|
50
|
+
Equivalent `just` recipes (`justfile`) are also available and wrap the same commands:
|
|
51
|
+
|
|
52
|
+
- `just check` (lint + test)
|
|
53
|
+
- `just test` / `just test-file spec/path_spec.rb:42`
|
|
54
|
+
- `just lint` (runs `rubocop -a`, auto-fixes)
|
|
55
|
+
- `just bump [major|minor|tiny]`
|
|
56
|
+
- `just release`
|
|
57
|
+
|
|
50
58
|
## Repository Layout
|
|
51
59
|
|
|
52
60
|
- `exe/pdfh` CLI entrypoint
|
|
@@ -59,7 +67,11 @@ Useful `mise` tasks (`mise.toml`):
|
|
|
59
67
|
## Processing Flow (High Level)
|
|
60
68
|
|
|
61
69
|
```text
|
|
62
|
-
exe/pdfh →
|
|
70
|
+
exe/pdfh → DependencyValidator (qpdf, pdftotext) → Main.start
|
|
71
|
+
↓
|
|
72
|
+
OptParser (ARGV)
|
|
73
|
+
↓
|
|
74
|
+
SettingsBuilder → SettingsValidator
|
|
63
75
|
↓
|
|
64
76
|
DirectoryScanner.scan (finds *.pdf)
|
|
65
77
|
↓
|
|
@@ -76,9 +88,9 @@ Key service objects in `lib/pdfh/services/`:
|
|
|
76
88
|
- `PdfTextExtractor` — shells out `pdftotext -enc UTF-8 -layout`
|
|
77
89
|
- `DocumentMatcher` — iterates `DocumentType`s; matches `re_id` then `re_date`
|
|
78
90
|
- `DocumentManager` — creates dest dir, copies/renames PDF, copies companion files
|
|
79
|
-
- `SettingsBuilder` —
|
|
91
|
+
- `SettingsBuilder` — resolves config from env/known locations (`.yml`/`.yaml`), validates, and builds `Settings`
|
|
80
92
|
- `SettingsValidator` — validates settings; raises `ArgumentError` on fatal errors, warns and skips on soft errors (e.g. missing lookup dirs)
|
|
81
|
-
- `OptParser` — parses `ARGV` into `RunOptions`
|
|
93
|
+
- `OptParser` — parses `ARGV` into `RunOptions`; supports `--list-types`, `-v/--verbose`, `-d/--dry`, `-V/--version`, `-h/--help`
|
|
82
94
|
|
|
83
95
|
## Code Style and Conventions
|
|
84
96
|
|
|
@@ -127,6 +139,7 @@ Side effects and IO:
|
|
|
127
139
|
- File system changes are centralized in `DocumentManager`.
|
|
128
140
|
- Respect `dry_run` / `--dry` to avoid writing files.
|
|
129
141
|
- When copying/moving files, preserve metadata if possible (`FileUtils` with `preserve: true`).
|
|
142
|
+
- After successful processing, source PDFs are moved to `*.bkp` in place (`DocumentManager#backup_original`).
|
|
130
143
|
|
|
131
144
|
Testing:
|
|
132
145
|
|
|
@@ -136,7 +149,9 @@ Testing:
|
|
|
136
149
|
|
|
137
150
|
## Configuration and Templates
|
|
138
151
|
|
|
139
|
-
- Configuration is loaded from `$PDFH_CONFIG_FILE`, then CWD, `~/.config
|
|
152
|
+
- Configuration is loaded from `$PDFH_CONFIG_FILE`, then CWD, then `$XDG_CONFIG_HOME` (default `~/.config`), then `~/`.
|
|
153
|
+
- Supported config file extensions are `.yml` and `.yaml`.
|
|
154
|
+
- If no config file is found, `SettingsBuilder` creates a default template at `~/pdfh.yml`.
|
|
140
155
|
- Document templates use `{placeholder}` tokens defined in `Document#rename_data`.
|
|
141
156
|
- Date regex supports named captures `(?<m>...)` / `(?<y>...)` / `(?<d>...)` or positional captures.
|
|
142
157
|
|
|
@@ -149,8 +164,10 @@ Testing:
|
|
|
149
164
|
- **v4 breaking change**: Config format changed in v4.0.0 — old configs are incompatible. `re_id` matches PDF *text content* (not filename). New placeholders: `{day}`, `{quarter}`, `{bimester}`, `{name}`.
|
|
150
165
|
- **Multi-match behavior**: PDFs matching more than one `DocumentType` are silently skipped (see `Main.start`).
|
|
151
166
|
- **`_unlocked` suffix**: `DocumentManager` strips `_unlocked` when locating companion files — legacy pattern from `qpdf` unlocking.
|
|
152
|
-
- **
|
|
167
|
+
- **First run side effect**: Missing config does not fail fast; it auto-creates `~/pdfh.yml` and continues from that template.
|
|
168
|
+
- **Dependency mismatch risk**: `qpdf` is still checked at startup (`exe/pdfh`) even though normal v4 processing does not call it directly.
|
|
153
169
|
- **Colorize in tests**: Logging output is colored via `colorize`; specs mock `Console` with shared context `"with silent console"` to suppress output.
|
|
170
|
+
- **CI only lints, not tests**: `.github/workflows/rubocop-analysis.yml` is the only CI workflow and runs RuboCop only (via CodeQL/SARIF upload); RSpec is not run in CI. Always run `mise run test` locally — a green CI check does not mean tests pass.
|
|
154
171
|
|
|
155
172
|
## Suggested Workflow for Agents
|
|
156
173
|
|
|
@@ -159,6 +176,10 @@ Testing:
|
|
|
159
176
|
- Run `mise run lint` and `mise run test` before finalizing changes.
|
|
160
177
|
- Keep changes localized; avoid touching unrelated files.
|
|
161
178
|
|
|
179
|
+
## Repo Etiquette
|
|
180
|
+
|
|
181
|
+
- Commit messages must follow Conventional Commits format (e.g. `fix:`, `feat:`, `chore:`) — enforced by the `conventional-pre-commit` `commit-msg` hook in `.pre-commit-config.yaml`. Non-conforming messages are rejected.
|
|
182
|
+
|
|
162
183
|
## Examples
|
|
163
184
|
|
|
164
185
|
Run a single spec:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v4.0.3 - 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Centralize `_unlocked` suffix handling in `FileInfo#stem`
|
|
6
|
+
- Upgrade gems
|
|
7
|
+
|
|
8
|
+
## v4.0.2 - 2026-08-03
|
|
9
|
+
|
|
10
|
+
- Skip `.bkp` files when copying companion files
|
|
11
|
+
|
|
12
|
+
## v4.0.1 - 2026-03-24
|
|
13
|
+
|
|
14
|
+
- Fix incorrect `require`
|
|
15
|
+
|
|
3
16
|
## v4.0.0 - 2026-03-21
|
|
4
17
|
|
|
5
18
|
### Breaking
|
data/Gemfile.lock
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
pdfh (4.0.
|
|
4
|
+
pdfh (4.0.3)
|
|
5
5
|
colorize (~> 1.1.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
activesupport (8.1.3)
|
|
10
|
+
activesupport (8.1.3.1)
|
|
11
11
|
base64
|
|
12
12
|
bigdecimal
|
|
13
13
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
@@ -22,57 +22,58 @@ GEM
|
|
|
22
22
|
uri (>= 0.13.1)
|
|
23
23
|
ast (2.4.3)
|
|
24
24
|
base64 (0.3.0)
|
|
25
|
-
bigdecimal (4.
|
|
25
|
+
bigdecimal (4.1.3)
|
|
26
26
|
code-scanning-rubocop (0.6.1)
|
|
27
27
|
rubocop (~> 1.0)
|
|
28
28
|
colorize (1.1.0)
|
|
29
|
-
concurrent-ruby (1.3.
|
|
29
|
+
concurrent-ruby (1.3.8)
|
|
30
30
|
connection_pool (3.0.2)
|
|
31
|
-
date (3.5.1)
|
|
32
31
|
debug (1.11.1)
|
|
33
32
|
irb (~> 1.10)
|
|
34
33
|
reline (>= 0.3.8)
|
|
35
34
|
diff-lcs (1.6.2)
|
|
36
35
|
docile (1.4.1)
|
|
37
36
|
drb (2.2.3)
|
|
38
|
-
erb (6.0.
|
|
39
|
-
factory_bot (6.
|
|
37
|
+
erb (6.0.7)
|
|
38
|
+
factory_bot (6.6.0)
|
|
40
39
|
activesupport (>= 6.1.0)
|
|
41
|
-
i18n (1.
|
|
40
|
+
i18n (1.15.2)
|
|
42
41
|
concurrent-ruby (~> 1.0)
|
|
43
|
-
io-console (0.
|
|
44
|
-
irb (1.
|
|
42
|
+
io-console (0.9.4)
|
|
43
|
+
irb (1.18.0)
|
|
45
44
|
pp (>= 0.6.0)
|
|
46
45
|
prism (>= 1.3.0)
|
|
47
46
|
rdoc (>= 4.0.0)
|
|
48
47
|
reline (>= 0.4.2)
|
|
49
|
-
json (
|
|
50
|
-
language_server-protocol (3.17.0.
|
|
48
|
+
json (3.0.2)
|
|
49
|
+
language_server-protocol (3.17.0.6)
|
|
51
50
|
lint_roller (1.1.0)
|
|
52
51
|
logger (1.7.0)
|
|
53
|
-
minitest (6.0.
|
|
52
|
+
minitest (6.0.6)
|
|
54
53
|
drb (~> 2.0)
|
|
55
54
|
prism (~> 1.5)
|
|
56
|
-
parallel (
|
|
57
|
-
parser (3.3.
|
|
55
|
+
parallel (2.2.0)
|
|
56
|
+
parser (3.3.12.0)
|
|
58
57
|
ast (~> 2.4.1)
|
|
59
58
|
racc
|
|
60
|
-
pp (0.6.
|
|
59
|
+
pp (0.6.4)
|
|
61
60
|
prettyprint
|
|
62
61
|
prettyprint (0.2.0)
|
|
63
62
|
prism (1.9.0)
|
|
64
|
-
psych (5.3.1)
|
|
65
|
-
date
|
|
66
|
-
stringio
|
|
67
63
|
racc (1.8.1)
|
|
68
64
|
rainbow (3.1.1)
|
|
69
|
-
rake (13.
|
|
70
|
-
|
|
65
|
+
rake (13.4.2)
|
|
66
|
+
rbs (4.2.0)
|
|
67
|
+
logger
|
|
68
|
+
prism (>= 1.6.0)
|
|
69
|
+
tsort
|
|
70
|
+
rdoc (8.0.0)
|
|
71
71
|
erb
|
|
72
|
-
|
|
72
|
+
prism (>= 1.6.0)
|
|
73
|
+
rbs (>= 4.0.0)
|
|
73
74
|
tsort
|
|
74
|
-
regexp_parser (2.
|
|
75
|
-
reline (0.
|
|
75
|
+
regexp_parser (2.13.0)
|
|
76
|
+
reline (0.7.0)
|
|
76
77
|
io-console (~> 0.5)
|
|
77
78
|
rspec (3.13.2)
|
|
78
79
|
rspec-core (~> 3.13.0)
|
|
@@ -87,33 +88,34 @@ GEM
|
|
|
87
88
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
88
89
|
rspec-support (~> 3.13.0)
|
|
89
90
|
rspec-support (3.13.7)
|
|
90
|
-
rubocop (1.
|
|
91
|
-
json (
|
|
91
|
+
rubocop (1.91.0)
|
|
92
|
+
json (>= 2.3)
|
|
92
93
|
language_server-protocol (~> 3.17.0.2)
|
|
93
94
|
lint_roller (~> 1.1.0)
|
|
94
|
-
parallel (
|
|
95
|
+
parallel (>= 1.10)
|
|
95
96
|
parser (>= 3.3.0.2)
|
|
96
97
|
rainbow (>= 2.2.2, < 4.0)
|
|
97
98
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
98
99
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
99
100
|
ruby-progressbar (~> 1.7)
|
|
100
101
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
101
|
-
rubocop-ast (1.
|
|
102
|
+
rubocop-ast (1.50.0)
|
|
102
103
|
parser (>= 3.3.7.2)
|
|
103
104
|
prism (~> 1.7)
|
|
104
105
|
rubocop-factory_bot (2.28.0)
|
|
105
106
|
lint_roller (~> 1.1)
|
|
106
107
|
rubocop (~> 1.72, >= 1.72.1)
|
|
107
|
-
rubocop-performance (1.
|
|
108
|
+
rubocop-performance (1.27.0)
|
|
108
109
|
lint_roller (~> 1.1)
|
|
109
|
-
rubocop (>= 1.
|
|
110
|
+
rubocop (>= 1.89.0, < 2.0)
|
|
110
111
|
rubocop-ast (>= 1.47.1, < 2.0)
|
|
111
112
|
rubocop-rake (0.7.1)
|
|
112
113
|
lint_roller (~> 1.1)
|
|
113
114
|
rubocop (>= 1.72.1)
|
|
114
|
-
rubocop-rspec (3.
|
|
115
|
+
rubocop-rspec (3.10.2)
|
|
115
116
|
lint_roller (~> 1.1)
|
|
116
|
-
|
|
117
|
+
regexp_parser (>= 2.0)
|
|
118
|
+
rubocop (~> 1.86, >= 1.86.2)
|
|
117
119
|
ruby-progressbar (1.13.0)
|
|
118
120
|
securerandom (0.4.1)
|
|
119
121
|
simplecov (0.22.0)
|
|
@@ -122,13 +124,12 @@ GEM
|
|
|
122
124
|
simplecov_json_formatter (~> 0.1)
|
|
123
125
|
simplecov-html (0.13.2)
|
|
124
126
|
simplecov_json_formatter (0.1.4)
|
|
125
|
-
stringio (3.2.0)
|
|
126
127
|
tsort (0.2.0)
|
|
127
128
|
tzinfo (2.0.6)
|
|
128
129
|
concurrent-ruby (~> 1.0)
|
|
129
|
-
unicode-display_width (3.
|
|
130
|
-
unicode-emoji (~> 4.
|
|
131
|
-
unicode-emoji (4.
|
|
130
|
+
unicode-display_width (3.3.0)
|
|
131
|
+
unicode-emoji (~> 4.3)
|
|
132
|
+
unicode-emoji (4.3.0)
|
|
132
133
|
uri (1.1.1)
|
|
133
134
|
|
|
134
135
|
PLATFORMS
|
|
@@ -150,4 +151,4 @@ DEPENDENCIES
|
|
|
150
151
|
simplecov (~> 0.22)
|
|
151
152
|
|
|
152
153
|
BUNDLED WITH
|
|
153
|
-
4.0.
|
|
154
|
+
4.0.21
|
|
@@ -4,8 +4,6 @@ module Pdfh
|
|
|
4
4
|
module Services
|
|
5
5
|
# Manages the documents, rename, move, etc.
|
|
6
6
|
class DocumentManager
|
|
7
|
-
PDF_UNLOCKED_MAGIC_SUFFIX = "_unlocked"
|
|
8
|
-
|
|
9
7
|
# @param document [Document]
|
|
10
8
|
# @param base_path [String]
|
|
11
9
|
# @param dry_run [Boolean]
|
|
@@ -86,19 +84,22 @@ module Pdfh
|
|
|
86
84
|
FileUtils.mv(source_file, backup_file) unless dry_run?
|
|
87
85
|
end
|
|
88
86
|
|
|
89
|
-
# Finds companion files by
|
|
90
|
-
#
|
|
91
|
-
#
|
|
87
|
+
# Finds companion files by using the document's clean stem, which already has the
|
|
88
|
+
# `_unlocked` suffix removed (see Pdfh::FileInfo#stem). This allows PDFs unlocked by
|
|
89
|
+
# qpdf to locate their original companion files (e.g., .xml, .txt) that were never
|
|
90
|
+
# renamed with the `_unlocked` suffix.
|
|
91
|
+
# Excludes PDF files and `.bkp` backup files (already-processed PDFs renamed by +backup_original+).
|
|
92
92
|
#
|
|
93
|
-
# @return [Array<String>] array of non-PDF files with the same base name
|
|
93
|
+
# @return [Array<String>] array of non-PDF, non-backup files with the same base name
|
|
94
94
|
# @example
|
|
95
95
|
# # If document is "cuenta_unlocked.pdf", searches for "cuenta.*"
|
|
96
|
-
# # Returns ["cuenta.xml", "cuenta.txt"] (excluding "cuenta.pdf")
|
|
96
|
+
# # Returns ["cuenta.xml", "cuenta.txt"] (excluding "cuenta.pdf" and "cuenta.pdf.bkp")
|
|
97
97
|
def companion_files
|
|
98
98
|
@companion_files ||= begin
|
|
99
|
-
base_name = document.file_info.stem
|
|
99
|
+
base_name = document.file_info.stem
|
|
100
100
|
Dir.glob(File.join(document.file_info.dir, "#{base_name}.*")).reject do |file|
|
|
101
|
-
File.extname(file)
|
|
101
|
+
ext = File.extname(file)
|
|
102
|
+
ext == ".pdf" || file.end_with?(".bkp")
|
|
102
103
|
end
|
|
103
104
|
end
|
|
104
105
|
end
|
data/lib/pdfh/utils/file_info.rb
CHANGED
|
@@ -4,6 +4,10 @@ module Pdfh
|
|
|
4
4
|
# Encapsulates file system metadata for a PDF file path.
|
|
5
5
|
# Responsible for all file name derivations (extension, base name, backup name, etc.).
|
|
6
6
|
class FileInfo
|
|
7
|
+
# Magic suffix added by qpdf when unlocking a PDF. Treated as noise: it is
|
|
8
|
+
# never part of the real document name.
|
|
9
|
+
PDF_UNLOCKED_MAGIC_SUFFIX = "_unlocked"
|
|
10
|
+
|
|
7
11
|
# @param file_path [String] Absolute or relative path to the PDF file
|
|
8
12
|
# @return [self]
|
|
9
13
|
def initialize(file_path)
|
|
@@ -13,9 +17,9 @@ module Pdfh
|
|
|
13
17
|
# @return [String] Full path to the file
|
|
14
18
|
attr_reader :path
|
|
15
19
|
|
|
16
|
-
# @return [String] File name without extension (e.g., "cuenta")
|
|
20
|
+
# @return [String] File name without extension or the `_unlocked` magic suffix (e.g., "cuenta")
|
|
17
21
|
def stem
|
|
18
|
-
File.basename(@path, extension)
|
|
22
|
+
File.basename(@path, extension).delete_suffix(PDF_UNLOCKED_MAGIC_SUFFIX)
|
|
19
23
|
end
|
|
20
24
|
alias name_only stem
|
|
21
25
|
|
data/lib/pdfh/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pdfh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Isaias Piña
|
|
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
98
98
|
- !ruby/object:Gem::Version
|
|
99
99
|
version: '0'
|
|
100
100
|
requirements: []
|
|
101
|
-
rubygems_version: 4.0.
|
|
101
|
+
rubygems_version: 4.0.21
|
|
102
102
|
specification_version: 4
|
|
103
103
|
summary: Organize PDF files
|
|
104
104
|
test_files: []
|