abbu 0.1.2 → 0.4.0
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/README.md +127 -17
- data/bin/abbu +185 -54
- data/docs/ABBU.md +226 -7
- data/docs/CHANGELOG.md +193 -0
- data/docs/CONTRIBUTING.md +61 -0
- data/docs/TODO.md +180 -0
- data/lib/abbu/archive.rb +68 -8
- data/lib/abbu/contact.rb +13 -4
- data/lib/abbu/diagnostic.rb +27 -0
- data/lib/abbu/exporters/csv_exporter.rb +26 -12
- data/lib/abbu/exporters/json_exporter.rb +17 -1
- data/lib/abbu/exporters/vcard_exporter.rb +91 -7
- data/lib/abbu/image_extractor.rb +134 -0
- data/lib/abbu/live_store.rb +104 -0
- data/lib/abbu/parse_error.rb +13 -0
- data/lib/abbu/parsers/plist_parser.rb +172 -4
- data/lib/abbu/parsers/sqlite_parser.rb +158 -55
- data/lib/abbu/query.rb +73 -0
- data/lib/abbu/schema_inspector.rb +133 -0
- data/lib/abbu/utils/contact_identity.rb +97 -0
- data/lib/abbu/utils/deduplicator.rb +130 -2
- data/lib/abbu/utils/image_resolver.rb +59 -0
- data/lib/abbu/utils/label_normalizer.rb +15 -0
- data/lib/abbu/utils/source_descriptor.rb +35 -0
- data/lib/abbu/version.rb +1 -1
- data/lib/abbu.rb +14 -2
- data/tasks/abbu.rake +1 -1
- metadata +50 -16
- data/CHANGELOG.md +0 -67
- data/bin/cleanse +0 -7
- data/bin/console +0 -10
- data/bin/dev +0 -7
- data/bin/lint +0 -7
- data/bin/outdated +0 -7
- data/bin/test +0 -7
data/docs/ABBU.md
CHANGED
|
@@ -9,9 +9,34 @@
|
|
|
9
9
|
They are **not** a single file format — they are a macOS "package" (a directory bundle that Finder
|
|
10
10
|
presents as a single file). This means you can inspect the contents with `ls` or `open -a Finder`.
|
|
11
11
|
|
|
12
|
+
## Evidence Standard
|
|
13
|
+
|
|
14
|
+
Apple does not publish a stable specification for every internal Contacts
|
|
15
|
+
archive schema represented by `.abbu` bundles. This document therefore
|
|
16
|
+
distinguishes observed repository-fixture behavior from documented Apple APIs
|
|
17
|
+
and from hypotheses that still require verification.
|
|
18
|
+
|
|
19
|
+
Never infer Apple Contacts storage semantics merely from Core Data table or
|
|
20
|
+
column names. Require observed fixture evidence, Apple documentation where
|
|
21
|
+
available, or reproducible verification, and record consequential discoveries
|
|
22
|
+
in this document.
|
|
23
|
+
|
|
24
|
+
For each new schema, relationship, source layout, image convention, or version
|
|
25
|
+
variation:
|
|
26
|
+
|
|
27
|
+
1. record the macOS or Contacts version when known;
|
|
28
|
+
2. identify the synthetic fixture, SQLite query, plist key path, file evidence,
|
|
29
|
+
or Apple documentation supporting the conclusion;
|
|
30
|
+
3. add a deterministic regression fixture and spec; and
|
|
31
|
+
4. label unresolved interpretations as hypotheses rather than format guarantees.
|
|
32
|
+
|
|
33
|
+
Real address-book exports contain sensitive personal data. Use them only for
|
|
34
|
+
local verification, sanitize the observed behavior into deterministic synthetic
|
|
35
|
+
fixtures, and never commit the original contacts, photos, or account identifiers.
|
|
36
|
+
|
|
12
37
|
## Structure
|
|
13
38
|
|
|
14
|
-
|
|
39
|
+
The supported synthetic fixtures and observed exports use layouts such as:
|
|
15
40
|
|
|
16
41
|
```text
|
|
17
42
|
Contacts.abbu/
|
|
@@ -36,12 +61,16 @@ Contacts.abbu/
|
|
|
36
61
|
|
|
37
62
|
### 1. SQLite (modern macOS)
|
|
38
63
|
|
|
39
|
-
|
|
64
|
+
Supported modern fixtures contain one or more SQLite databases named:
|
|
40
65
|
|
|
41
66
|
```
|
|
42
67
|
AddressBook-v22.abcddb
|
|
43
68
|
```
|
|
44
69
|
|
|
70
|
+
The following tables and mappings are exercised by the repository's generated
|
|
71
|
+
SQLite fixture and parser specs. Their names alone are not evidence that the
|
|
72
|
+
same semantics apply to every macOS version.
|
|
73
|
+
|
|
45
74
|
Key tables:
|
|
46
75
|
|
|
47
76
|
| Table | Purpose |
|
|
@@ -77,12 +106,194 @@ Notable columns in `ZABCDRECORD`:
|
|
|
77
106
|
| `ZPRONOUNS` | Pronouns |
|
|
78
107
|
| `ZRINGTONE` | Ringtone |
|
|
79
108
|
| `ZTEXTTONE` | Text tone |
|
|
109
|
+
| `ZCREATIONDATE` | Optional record creation timestamp |
|
|
110
|
+
| `ZMODIFICATIONDATE` | Optional record modification timestamp |
|
|
111
|
+
|
|
112
|
+
### Timestamp and source provenance
|
|
113
|
+
|
|
114
|
+
Observed Contacts databases may include `ZCREATIONDATE` and `ZMODIFICATIONDATE` on
|
|
115
|
+
`ZABCDRECORD`. ABBU interprets numeric values in those columns as Apple absolute time:
|
|
116
|
+
seconds since 2001-01-01 00:00:00 UTC. The columns are optional because exported schemas
|
|
117
|
+
vary across macOS releases and account providers; when either column is absent or invalid,
|
|
118
|
+
the corresponding `Contact` value is `nil`.
|
|
119
|
+
|
|
120
|
+
These values describe timestamps stored on the record. They must not be interpreted as
|
|
121
|
+
proof of a user-initiated creation or edit, because syncing and migration can also affect
|
|
122
|
+
them.
|
|
123
|
+
|
|
124
|
+
Every parsed contact includes source provenance with the absolute source path, its path
|
|
125
|
+
relative to the `.abbu` root, and whether it came from the root bundle or a database under
|
|
126
|
+
`Sources/<identifier>/`. Legacy plist contacts receive the same file-level provenance.
|
|
127
|
+
|
|
128
|
+
### Opt-in live Contacts stores
|
|
129
|
+
|
|
130
|
+
The CLI uses `--live` only for auto-discovery and `--live-path PATH` for an explicit
|
|
131
|
+
store. These forms are mutually exclusive and accept no positional archive/path arguments.
|
|
132
|
+
Option ordering does not change input selection. Synthetic WAL-mode and concurrent-writer
|
|
133
|
+
validation remains a follow-up; read-only handles do not establish snapshot consistency
|
|
134
|
+
across an actively changing Contacts store.
|
|
135
|
+
|
|
136
|
+
`Abbu.open_live` and the CLI's live modes can read an AddressBook directory
|
|
137
|
+
without first exporting an `.abbu` archive. This mode is deliberately separate from
|
|
138
|
+
`Abbu.open`: archive validation and plist fallback do not apply to a live store.
|
|
139
|
+
|
|
140
|
+
When no path is supplied on macOS, ABBU checks the observed per-user location at
|
|
141
|
+
`~/Library/Application Support/AddressBook`. Callers may instead provide a directory,
|
|
142
|
+
which keeps automation and tests independent of the host platform and user account.
|
|
143
|
+
ABBU discovers databases directly under that directory and one level below
|
|
144
|
+
`Sources/<identifier>/`, preserving the same root/source provenance used for archives.
|
|
145
|
+
Only files matching the observed `AddressBook-v*.abcddb` shape are candidates; this
|
|
146
|
+
filename pattern is discovery evidence, not a guarantee of stable Apple semantics.
|
|
147
|
+
|
|
148
|
+
Every live-store database is opened using SQLite's read-only mode. ABBU has no live-store
|
|
149
|
+
write API and never creates, updates, or deletes Contacts data. macOS privacy controls may
|
|
150
|
+
deny access even when the path exists. In that case ABBU raises
|
|
151
|
+
`Abbu::LiveStore::PermissionError` with instructions to grant the calling terminal or
|
|
152
|
+
application Full Disk Access in **System Settings → Privacy & Security → Full Disk
|
|
153
|
+
Access**. Missing directories and databases raise `Abbu::LiveStore::NotFoundError`, and
|
|
154
|
+
automatic discovery without a caller-supplied path raises
|
|
155
|
+
`Abbu::LiveStore::UnsupportedPlatformError` outside macOS.
|
|
156
|
+
|
|
157
|
+
The repository verifies live-store behavior only with deterministic synthetic SQLite
|
|
158
|
+
fixtures. It does not inspect or commit a developer's real Contacts store.
|
|
159
|
+
### Provenance-aware identity evidence
|
|
160
|
+
|
|
161
|
+
ABBU treats deduplication as a suggestion boundary rather than proof that two records are
|
|
162
|
+
the same person. `Utils::Deduplicator#matches` compares normalized email, phone, name, and
|
|
163
|
+
organization signals while returning both original contacts, both source records, raw
|
|
164
|
+
evidence, normalized comparison values, confidence, and ambiguity status.
|
|
165
|
+
|
|
166
|
+
Email comparison trims surrounding whitespace and applies Unicode-aware case folding.
|
|
167
|
+
Names and organizations use Unicode NFKC normalization, case folding, and whitespace or
|
|
168
|
+
punctuation normalization without transliterating distinct characters. Explicit `+` and
|
|
169
|
+
`00` phone forms are compared as international numbers. The trimmed raw value must start
|
|
170
|
+
with a literal ASCII `+` or contiguous `00`; punctuation removal never establishes an
|
|
171
|
+
international prefix. For example, `(001) 512-555-0100` remains national-format evidence.
|
|
172
|
+
National-format numbers remain
|
|
173
|
+
source-local evidence because ABBU has no country or numbering-plan evidence with which
|
|
174
|
+
to infer a global identity.
|
|
175
|
+
|
|
176
|
+
SQLite primary keys, source identifiers, private Apple link identifiers, and image stems
|
|
177
|
+
are not treated as global contact identifiers. The repository fixtures do not establish
|
|
178
|
+
such semantics. Competing candidates and weak name/organization or source-local phone
|
|
179
|
+
matches remain ambiguous, and no contact is merged unless the caller supplies an explicit
|
|
180
|
+
merge policy.
|
|
181
|
+
|
|
182
|
+
### Image resolution and extraction
|
|
183
|
+
|
|
184
|
+
The synthetic SQLite fixture demonstrates a `ZIMAGEURI` value whose stem matches a file
|
|
185
|
+
under an `Images/` directory. Resolution covers the root bundle and nested
|
|
186
|
+
`Sources/<identifier>/Images/` directories. When duplicate stems exist, ABBU uses the
|
|
187
|
+
contact's database provenance to select only an image beside that database; it does not
|
|
188
|
+
guess when the available evidence remains ambiguous.
|
|
189
|
+
|
|
190
|
+
`Archive#extract_images(output_dir)` and the CLI `--extract-images DIR` copy resolved
|
|
191
|
+
images without changing `Contact#image_uri`, `Contact#image_path`, or `Contact#source`.
|
|
192
|
+
Exported filenames combine a sanitized contact name, the original image identifier, and
|
|
193
|
+
a stable provenance digest. Path separators, control characters, and reserved filename
|
|
194
|
+
characters cannot create subdirectories or traverse outside the selected output directory.
|
|
195
|
+
|
|
196
|
+
The selected output directory (including symlinked parents) is resolved to its canonical
|
|
197
|
+
directory before copying; callers must control that directory and prevent concurrent
|
|
198
|
+
directory replacement. Each image is created exclusively with owner-only permissions.
|
|
199
|
+
Existing destinations are never overwritten, including regular files, hard links, and
|
|
200
|
+
symlinks (even dangling ones). These collisions produce a `destination_exists` extraction
|
|
201
|
+
diagnostic and no successful file record; other images continue. Repeating extraction
|
|
202
|
+
into the same directory therefore reports collisions instead of replacing earlier output.
|
|
203
|
+
Directory creation/resolution failures raise filesystem errors before extraction begins.
|
|
204
|
+
|
|
205
|
+
Extraction diagnostics are a separate API from `archive.diagnostics`: they may contain
|
|
206
|
+
contact names, raw image identifiers, source paths, and filesystem error details. Treat
|
|
207
|
+
them and the CLI's image warnings as sensitive contact data, not safe-to-publish logs.
|
|
208
|
+
|
|
209
|
+
ABBU recognizes JPEG, PNG, GIF, and common HEIF/HEIC-compatible brands from file
|
|
210
|
+
signatures and chooses the exported extension from those bytes rather than the source
|
|
211
|
+
extension. Unknown content is reported as a diagnostic instead of being relabeled. HEIC
|
|
212
|
+
data is copied unchanged; ABBU does not transcode it.
|
|
213
|
+
|
|
214
|
+
No repository fixture currently demonstrates a reliable Apple thumbnail-versus-full-size
|
|
215
|
+
naming or selection rule. ABBU therefore exports the image resolved by the observed
|
|
216
|
+
`ZIMAGEURI` relationship and does not infer size semantics from filenames or directories.
|
|
217
|
+
|
|
218
|
+
### Recovery and diagnostics
|
|
219
|
+
|
|
220
|
+
By default, ABBU recovers from malformed individual plist records, missing
|
|
221
|
+
optional SQLite relationship tables, and unresolved image references. Each
|
|
222
|
+
recovery appends an `Abbu::Diagnostic` to `archive.diagnostics` with a category,
|
|
223
|
+
parser, source path, non-PII context, and a stable message. Required contact
|
|
224
|
+
schema failures still raise because no evidence-backed contact record can be
|
|
225
|
+
recovered safely.
|
|
226
|
+
|
|
227
|
+
An absent optional SQLite table produces one diagnostic per database and table,
|
|
228
|
+
regardless of contact count. ABBU does not place record identifiers or raw image
|
|
229
|
+
references in these schema- and image-level diagnostic contexts.
|
|
230
|
+
|
|
231
|
+
Pass `strict: true` to `Abbu.open` or `--strict` to the CLI to raise
|
|
232
|
+
`Abbu::ParseError` on the first recoverable condition. The CLI prints a
|
|
233
|
+
diagnostic summary to standard error so exported data on standard output remains
|
|
234
|
+
pipeable.
|
|
235
|
+
|
|
236
|
+
### Schema diagnostics
|
|
237
|
+
|
|
238
|
+
`Archive#schema_report` and `abbu Contacts.abbu --schema` inspect every discovered
|
|
239
|
+
SQLite database and return deterministic schema metadata. Reports identify recognized
|
|
240
|
+
and unrecognized tables and columns, recognized items that are absent, declared SQLite
|
|
241
|
+
types, primary-key and nullability metadata, source provenance, and exact owner/contact-
|
|
242
|
+
style column names that may represent contact links.
|
|
243
|
+
|
|
244
|
+
These reports are research evidence, not parser mappings. In particular, a
|
|
245
|
+
`contact_link_candidate` flag records only an exact column-name shape such as `ZOWNER`,
|
|
246
|
+
`ZCONTACT`, or `Z_CONTACT`; it does not claim a foreign-key target or assign Apple
|
|
247
|
+
Contacts semantics. Unknown tables and columns must be reproduced in a sanitized fixture
|
|
248
|
+
or supported by documentation before ABBU uses them to populate contacts.
|
|
249
|
+
|
|
250
|
+
Missing recognized tables and columns remain visible as diagnostic observations. The
|
|
251
|
+
parser tolerates absent established email, phone, and postal-address tables by returning
|
|
252
|
+
empty collections, while the schema report preserves the absence for compatibility
|
|
253
|
+
research. If one of those tables exists but lacks an expected column, parsing raises the
|
|
254
|
+
SQLite schema error instead of silently treating the contact as having no corresponding
|
|
255
|
+
data. The core `ZABCDRECORD` table remains required for contact parsing.
|
|
256
|
+
|
|
257
|
+
### Labeled values
|
|
258
|
+
|
|
259
|
+
The synthetic SQLite and plist fixtures include both custom labels and Apple's
|
|
260
|
+
observed standard-label wrapper, such as `_$!<Work>!$_`. ABBU exposes the
|
|
261
|
+
human-facing value as `label` (`Work`) and preserves the exact stored value as
|
|
262
|
+
`raw_label`. Custom, blank, malformed, Unicode, and already-normalized labels
|
|
263
|
+
are not otherwise rewritten. Direct plist keys such as `Birthday` have no
|
|
264
|
+
stored label, so their normalized label is derived from the key and
|
|
265
|
+
`raw_label` is `nil`.
|
|
266
|
+
|
|
267
|
+
Normalization applies to email addresses, phone numbers, postal addresses,
|
|
268
|
+
URLs, related names, date components, and instant-message handles. JSON keeps
|
|
269
|
+
both values. Human-facing CSV uses normalized labels, while vCard anniversary
|
|
270
|
+
labels prefer `raw_label` so Apple label wrappers and custom source values
|
|
271
|
+
survive parse → model → interchange export.
|
|
80
272
|
|
|
81
273
|
### 2. Plist / `.abcdp` (legacy macOS)
|
|
82
274
|
|
|
83
|
-
Older macOS versions stored
|
|
84
|
-
|
|
85
|
-
|
|
275
|
+
Older macOS versions stored contacts as separate plist files under `Records/`.
|
|
276
|
+
The repository fixtures demonstrate dictionaries that the plist parser
|
|
277
|
+
normalizes into the same contact model used by the SQLite parser. Additional
|
|
278
|
+
plist keys or layouts require fixture evidence before they are treated as
|
|
279
|
+
supported semantics.
|
|
280
|
+
|
|
281
|
+
## Repository Evidence
|
|
282
|
+
|
|
283
|
+
- `spec/fixtures/TestContacts.abbu/` exercises the supported synthetic SQLite,
|
|
284
|
+
nested source, and image-resolution behavior.
|
|
285
|
+
- `spec/fixtures/PlistContacts.abbu/` exercises the supported synthetic legacy
|
|
286
|
+
plist behavior.
|
|
287
|
+
- `spec/fixtures/identity_cases.yml` contains deterministic synthetic cross-source and
|
|
288
|
+
Unicode near-collision identity evidence.
|
|
289
|
+
- `spec/support/fixture_generator.rb` is the reproducible source for generated
|
|
290
|
+
SQLite fixture structure and data.
|
|
291
|
+
- `spec/abbu/schema_inspector_spec.rb` builds deterministic temporary SQLite
|
|
292
|
+
schemas for missing tables, unknown contact-link candidates, and column drift.
|
|
293
|
+
|
|
294
|
+
These fixtures prove only the variations they contain. Table names, column
|
|
295
|
+
names, entity numbers, UUIDs, and directory names alone are not sufficient
|
|
296
|
+
evidence for new behavior.
|
|
86
297
|
|
|
87
298
|
## Export Steps
|
|
88
299
|
|
|
@@ -96,9 +307,17 @@ The "Contacts Archive" option produces a `.abbu` bundle.
|
|
|
96
307
|
|
|
97
308
|
## References
|
|
98
309
|
|
|
99
|
-
- [Apple Contacts
|
|
310
|
+
- [Apple Contacts framework](https://developer.apple.com/documentation/contacts)
|
|
311
|
+
- [iQueryContacts forensic schema notes](https://github.com/MetadataForensics/iQueryContacts)
|
|
312
|
+
- [Observed Contacts timestamp epoch](https://apple.stackexchange.com/questions/115551/how-to-sort-contacts-by-creation-date-or-modification-date-in-ios-contacts-or-os/229313)
|
|
313
|
+
- [LifeOS Apple Contacts timestamp conversion](https://github.com/nbramia/LifeOS/blob/main/scripts/apple_data_export.py)
|
|
100
314
|
- [SQLite3 gem](https://github.com/sparklemotion/sqlite3-ruby)
|
|
101
|
-
-
|
|
315
|
+
- [macos-ts live Contacts reader](https://github.com/evantahler/macos-ts)
|
|
316
|
+
- Repository fixtures and regression specs listed above
|
|
317
|
+
|
|
318
|
+
Apple's public Contacts framework documents application-facing concepts, not a
|
|
319
|
+
stable `.abbu` storage contract. Private framework names and Core Data names are
|
|
320
|
+
not normative references.
|
|
102
321
|
|
|
103
322
|
---
|
|
104
323
|
Stan Carver II
|
data/docs/CHANGELOG.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<!-- CHANGELOG.md -->
|
|
2
|
+
|
|
3
|
+
# Changelog
|
|
4
|
+
|
|
5
|
+
All notable changes to `abbu` are documented here.
|
|
6
|
+
|
|
7
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
8
|
+
Versioning follows [Semantic Versioning](https://semver.org/).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
## [0.4.0] - 2026-09-30
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Live CLI input uses distinct `--live` auto-discovery and `--live-path PATH` forms;
|
|
19
|
+
mixed modes and unexpected live positional arguments are rejected.
|
|
20
|
+
|
|
21
|
+
- Deduplication recognizes international phone prefixes from trimmed raw input, not
|
|
22
|
+
punctuation-stripped digits, keeping national-looking `(001)` values source-local.
|
|
23
|
+
|
|
24
|
+
- Image extraction exclusively creates destination files and reports `destination_exists`
|
|
25
|
+
rather than overwriting existing files, symlinks, or hard links. Output directory aliases
|
|
26
|
+
are resolved before extraction; extraction diagnostic privacy is documented.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Explicit, read-only live Contacts access through `Abbu.open_live` and
|
|
31
|
+
`abbu --live` / `abbu --live-path PATH`, with root and `Sources/*` database discovery
|
|
32
|
+
- Actionable live-store errors for missing databases, unsupported automatic
|
|
33
|
+
discovery, and macOS Full Disk Access restrictions
|
|
34
|
+
- Platform-independent synthetic coverage for live-store discovery, source
|
|
35
|
+
provenance, SQLite read-only enforcement, and CLI behavior
|
|
36
|
+
- Provenance-aware `Utils::Deduplicator#matches` suggestions with normalized email,
|
|
37
|
+
international/source-local phone, Unicode name, and organization evidence
|
|
38
|
+
- Match confidence, ambiguity status, original source records, and raw evidence, plus an
|
|
39
|
+
explicit callable merge-policy boundary that never silently collapses contacts
|
|
40
|
+
|
|
41
|
+
- `Archive#extract_images` and `Abbu::ImageExtractor` for copying contact photos to a
|
|
42
|
+
caller-selected directory with structured missing, unreadable, and unsupported-image
|
|
43
|
+
diagnostics
|
|
44
|
+
- `--extract-images DIR` CLI support with content-aware JPEG, PNG, GIF, and HEIC extension
|
|
45
|
+
selection and safe, Unicode-preserving, collision-resistant filenames
|
|
46
|
+
- Source-local resolution for duplicate image stems in nested account directories without
|
|
47
|
+
guessing when provenance cannot disambiguate candidates
|
|
48
|
+
|
|
49
|
+
- Structured tolerant-parsing diagnostics with strict API/CLI mode for corrupt
|
|
50
|
+
plist records, missing optional SQLite data, and unresolved image references
|
|
51
|
+
- Missing optional SQLite tables emit one non-PII diagnostic per database and
|
|
52
|
+
table instead of repeating schema-level warnings for every contact
|
|
53
|
+
- Evidence-safe SQLite schema diagnostics through `Archive#schema_report` and
|
|
54
|
+
`abbu <archive> --schema`, including unknown tables/columns, absent recognized
|
|
55
|
+
schema elements, and owner/contact-style relationship candidates
|
|
56
|
+
- Deterministic schema-variation coverage for missing optional tables, unknown
|
|
57
|
+
contact-linked tables, and column drift
|
|
58
|
+
- Normalized Apple standard labels with the original source value preserved as
|
|
59
|
+
`raw_label` on labeled contact values
|
|
60
|
+
- Chainable `Abbu::Query` and `Archive#where` APIs for contact filtering
|
|
61
|
+
- Exact normalized email and phone lookup that returns all matches across sources
|
|
62
|
+
- Case-insensitive partial name and email search through Ruby and tab-separated CLI output
|
|
63
|
+
- Stable `--json` search output using the regular contact JSON schema
|
|
64
|
+
- Contact creation and modification timestamps from optional SQLite `ZCREATIONDATE` and `ZMODIFICATIONDATE` columns
|
|
65
|
+
- Provenance metadata identifying each contact's source database or plist and its location within the ABBU bundle
|
|
66
|
+
- Creation, modification, and source metadata in JSON exports
|
|
67
|
+
- Repository constitution and focused local skills for ABBU format evidence,
|
|
68
|
+
Ruby gem development, testing, and Sheriff-gated releases
|
|
69
|
+
- Canonical `bin/spec` and `bin/package` workflows, with CI reusing project-local
|
|
70
|
+
`bin/spec` and `bin/lint` instead of duplicating their commands
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- CI now runs the Ruby matrix once for pull requests and on pushes to canonical `main`, avoiding duplicate feature-branch push and pull-request runs
|
|
75
|
+
- SQLite parsing now tolerates absent established email, phone, and postal-address
|
|
76
|
+
tables and returns empty collections while retaining the variation in schema diagnostics;
|
|
77
|
+
unexpected column drift and other SQL errors on present tables continue to surface
|
|
78
|
+
- vCard anniversary export now prefers the original `raw_label` so Apple and
|
|
79
|
+
custom source representations survive parse-and-export round trips
|
|
80
|
+
- Minimum supported Ruby and RuboCop target are now 3.3; CI covers Ruby 3.3,
|
|
81
|
+
3.4, and 4.0, with Ruby 3.3 as the designated lint/tooling job
|
|
82
|
+
- Agent guidance is consolidated in `AGENTS.md`; the redundant `CLAUDE.md` has
|
|
83
|
+
been removed
|
|
84
|
+
- Gem packaging now includes only the public `bin/abbu` executable instead of
|
|
85
|
+
repository-only developer commands
|
|
86
|
+
|
|
87
|
+
## [0.3.0] - 2026-09-29
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- `Contact#image_uri` and `Contact#image_path` accessors
|
|
92
|
+
- `Parsers::SqliteParser` extracts `ZIMAGEURI` from `ZABCDRECORD`
|
|
93
|
+
- `Utils::ImageResolver` builds an index of every image file under the bundle's `**/Images/` directories (jpg, jpeg, png, heic — case-insensitive) and resolves a contact's `image_uri` to a `Pathname` within the bundle
|
|
94
|
+
- `Archive#contacts` automatically resolves `image_path` for any contact that has a non-nil `image_uri`
|
|
95
|
+
- CSV export: new `ImagePath` column (last column)
|
|
96
|
+
- JSON export: `image_uri` and `image_path` fields (omitted via `.compact` when blank)
|
|
97
|
+
- vCard export: `PHOTO;VALUE=URI:file://<absolute path>` line, emitted when `image_path` is present
|
|
98
|
+
- `spec/fixtures/TestContacts.abbu/Images/stan-photo.jpg` stub image, regenerated by `spec/support/fixture_generator.rb`
|
|
99
|
+
|
|
100
|
+
### Notes
|
|
101
|
+
|
|
102
|
+
- PlistParser does not extract images — legacy `.abcdp` contacts typically embed image data inline, which is a separate extraction path
|
|
103
|
+
- vCard `PHOTO` references the absolute bundle path; base64 embedding and the `--extract-images` CLI flag are tracked for follow-up
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- vCard `PHOTO` file URIs now percent-encode spaces, reserved characters, and non-ASCII bytes in image paths
|
|
108
|
+
- `abbu:export` Rake task wrote hash literals (e.g. `{:address=>"…", :label=>"…"}`) into the `Email` and `Phone` CSV columns. Now correctly extracts the first address and number from each contact's multi-value field.
|
|
109
|
+
|
|
110
|
+
## [0.2.0] - 2026-05-03
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
|
|
114
|
+
- `Parsers::PlistParser` — full implementation of legacy `.abcdp` plist contact parsing, replacing the previous stub
|
|
115
|
+
- `PlistParser::FIELD_MAP` constant for flat-field mapping
|
|
116
|
+
- Multi-value field extraction for emails, phones, addresses, URLs, notes, related names, and social profiles
|
|
117
|
+
- `Archive` recursively scans `**/*.abcdp` across the entire bundle tree
|
|
118
|
+
- `plist` gem (~> 3.7) runtime dependency
|
|
119
|
+
- Plist fixture files (`spec/fixtures/PlistContacts.abbu/`) for integration testing
|
|
120
|
+
- `middle_name` attribute on `Contact`; `ZMIDDLENAME` / `Middle` plist key
|
|
121
|
+
- `dates` attribute on `Contact` (array of `{ year:, month:, day:, label: }` hashes)
|
|
122
|
+
- `birthday`, `anniversary`, and `lunar_birthday` accessor methods derived from the dates list
|
|
123
|
+
- `instant_messages` attribute (AIM, Jabber, Skype, etc.); `ZABCDMESSAGINGADDRESS` / `InstantMessage` key
|
|
124
|
+
- `verification_code` attribute on `Contact`; `ZVERIFICATIONCODE` column / `VerificationCode` plist key
|
|
125
|
+
- `phonetic_middle_name` attribute; `ZPHONETICMIDDLENAME` column / `PhoneticMiddle` plist key
|
|
126
|
+
- `ZABCDDATECOMPONENTS` parsing (year/month/day split columns)
|
|
127
|
+
- `BDAY`, `X-LUNAR-BDAY`, `X-ABDATE`, `X-ABLABEL` vCard fields
|
|
128
|
+
- `IMPP` vCard field for instant messaging
|
|
129
|
+
- Birthday, anniversary, lunar birthday, instant messages, and verification code in CSV and JSON exports
|
|
130
|
+
- Plist fixture for testing all the above (`spec/fixtures/PlistContacts.abbu/`)
|
|
131
|
+
|
|
132
|
+
### Changed
|
|
133
|
+
|
|
134
|
+
- `Contact#full_name` now includes middle name (`Honorable Stan The Man "Stretch" Carver II`)
|
|
135
|
+
- vCard `N` field includes middle name component
|
|
136
|
+
- `SqliteParser::RECORD_FIELD_MAP` extended with the new column → attr mappings
|
|
137
|
+
- `JsonExporter#contact_hash` includes all new fields; blank fields are dropped via `.compact`
|
|
138
|
+
- `CsvExporter` extended-field section now exports lunar birthday and verification code
|
|
139
|
+
|
|
140
|
+
## [0.1.2] - 2026-04-26
|
|
141
|
+
|
|
142
|
+
### Added
|
|
143
|
+
|
|
144
|
+
- Full Apple Contacts schema support: job title, department, maiden name, phonetic names, pronouns, ringtone, texttone
|
|
145
|
+
- Relational table parsing: URLs, notes, related names (family/business), social profiles (Twitter, etc.)
|
|
146
|
+
- Nickname, prefix, and suffix fields with smart `full_name` formatting
|
|
147
|
+
- Hash-based email/phone data preserving custom labels (e.g. "Direct Line", "Work")
|
|
148
|
+
- Address, group, URL, notes, related names, and social profiles in CSV export
|
|
149
|
+
- Comprehensive JSON export with all contact fields
|
|
150
|
+
- vCard 3.0 export with ADR, URL, NICKNAME, TITLE, NOTE, X-SOCIALPROFILE
|
|
151
|
+
- `rubocop-rspec` plugin integration
|
|
152
|
+
- 100% line coverage across all 44 specs
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- Refactored `SqliteParser` to use `RECORD_FIELD_MAP` constant for maintainability
|
|
157
|
+
- Refactored `CsvExporter` into `core_fields`/`extended_fields` for cleaner ABC metrics
|
|
158
|
+
- All specs comply with rubocop-rspec conventions
|
|
159
|
+
|
|
160
|
+
## [0.1.1] - 2026-04-23
|
|
161
|
+
|
|
162
|
+
### Fixed
|
|
163
|
+
|
|
164
|
+
- `require 'pathname'` missing in `archive.rb` causing `NameError` in isolation
|
|
165
|
+
- Added regression guard spec for file require isolation
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
## [0.1.0] - 2026-04-12
|
|
169
|
+
|
|
170
|
+
### Added
|
|
171
|
+
|
|
172
|
+
- `Abbu.open(path)` entry point returning an `Archive`
|
|
173
|
+
- `Archive#contacts` — reads contacts from SQLite or falls back to plist stub
|
|
174
|
+
- `Archive#sqlite?` — detects modern `.abcddb` bundles
|
|
175
|
+
- `Contact` object with `first_name`, `last_name`, `emails`, `phones`, `company`, `full_name`
|
|
176
|
+
- `Parsers::SqliteParser` — queries `ZABCDRECORD`, `ZABCDEMAILADDRESS`, `ZABCDPHONENUMBER`
|
|
177
|
+
- `Parsers::PlistParser` — stub with warning (legacy `.abcdp` support in v0.2)
|
|
178
|
+
- `Exporters::CsvExporter` — `to_file` and `to_stdout`
|
|
179
|
+
- `Exporters::JsonExporter` — `to_file` and `to_stdout`
|
|
180
|
+
- `Exporters::VcardExporter` — `to_file` and `to_stdout` (vCard 3.0)
|
|
181
|
+
- `Utils::Deduplicator` — groups contacts by first email, returns duplicates hash
|
|
182
|
+
- `bin/abbu` CLI with `--format`, `--output`, `--stats`, `--dedupe`, `--version`
|
|
183
|
+
- Rake tasks: `abbu:export`, `abbu:dedupe`, `abbu:stats`
|
|
184
|
+
- Example scripts: CSV, JSON, vCard, API, CRM sync, stats, dedupe
|
|
185
|
+
- `docs/ABBU.md` — file format reference
|
|
186
|
+
- RSpec test suite with 100% coverage target
|
|
187
|
+
- Guard + RuboCop DX loop
|
|
188
|
+
- GitHub Actions CI (Ruby 3.2 + 3.3)
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
Stan Carver II
|
|
192
|
+
Made in Texas 🤠
|
|
193
|
+
https://stancarver.com
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!-- CONTRIBUTING.md -->
|
|
2
|
+
|
|
3
|
+
# Contributing to abbu
|
|
4
|
+
|
|
5
|
+
Thank you for your interest in contributing!
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/scarver2/abbu
|
|
11
|
+
cd abbu
|
|
12
|
+
mise exec -- bundle install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## DX Loop
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
mise exec -- bundle exec guard
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This runs RSpec and RuboCop automatically on file changes.
|
|
22
|
+
|
|
23
|
+
## Running Tests
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bin/spec
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Linting
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bin/lint
|
|
33
|
+
mise exec -- bundle exec rubocop -a # autocorrect
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Packaging
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bin/package
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This builds the current gem, verifies its metadata, installs it into an isolated
|
|
43
|
+
gem home, and loads that installed copy. A successful package check is evidence
|
|
44
|
+
only; it does not authorize a release tag or RubyGems publication.
|
|
45
|
+
|
|
46
|
+
## Pull Request Guidelines
|
|
47
|
+
|
|
48
|
+
- Base branch: `main`
|
|
49
|
+
- Commit style: [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `chore:`)
|
|
50
|
+
- All specs must pass and coverage must remain at 100%
|
|
51
|
+
- RuboCop must pass with no offenses
|
|
52
|
+
- Add an entry to `docs/CHANGELOG.md` under `[Unreleased]`
|
|
53
|
+
|
|
54
|
+
## Reporting Issues
|
|
55
|
+
|
|
56
|
+
Open an issue on GitHub with a minimal reproduction case.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
Stan Carver II
|
|
60
|
+
Made in Texas 🤠
|
|
61
|
+
https://stancarver.com
|