abbu 0.1.0 → 0.1.2
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/CHANGELOG.md +28 -0
- data/docs/ABBU.md +30 -11
- data/lib/abbu/archive.rb +1 -0
- data/lib/abbu/contact.rb +11 -2
- data/lib/abbu/exporters/csv_exporter.rb +36 -2
- data/lib/abbu/exporters/json_exporter.rb +23 -8
- data/lib/abbu/exporters/vcard_exporter.rb +42 -3
- data/lib/abbu/parsers/sqlite_parser.rb +104 -11
- data/lib/abbu/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a2a430e28fa7792cb9f22a2e30f808a5d5752b0144cec428e64a88bde5740f9
|
|
4
|
+
data.tar.gz: 0c1e039e876c8a82b6a8ec52a046afe421f51d0236ceed9eab67f99a09261695
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5978d9208cac2c0a6bff53184b1dcd1fa016c204a34aa4a7912f9a814d616d4bc7a482d868bf3dc47a4fc2a1b6671bbf7c8092432c28eae2fb1ec3f628248822
|
|
7
|
+
data.tar.gz: bd915874f2b2987640a2942beaec9fee6e034411c81fbfa0df127136e60353c5cc0327d638a84a2e959600209d2e9ba72143768b39568484ce4bcbb498e486f4
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,34 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.1.2] - 2026-04-26
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Full Apple Contacts schema support: job title, department, maiden name, phonetic names, pronouns, ringtone, texttone
|
|
19
|
+
- Relational table parsing: URLs, notes, related names (family/business), social profiles (Twitter, etc.)
|
|
20
|
+
- Nickname, prefix, and suffix fields with smart `full_name` formatting
|
|
21
|
+
- Hash-based email/phone data preserving custom labels (e.g. "Direct Line", "Work")
|
|
22
|
+
- Address, group, URL, notes, related names, and social profiles in CSV export
|
|
23
|
+
- Comprehensive JSON export with all contact fields
|
|
24
|
+
- vCard 3.0 export with ADR, URL, NICKNAME, TITLE, NOTE, X-SOCIALPROFILE
|
|
25
|
+
- `rubocop-rspec` plugin integration
|
|
26
|
+
- 100% line coverage across all 44 specs
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Refactored `SqliteParser` to use `RECORD_FIELD_MAP` constant for maintainability
|
|
31
|
+
- Refactored `CsvExporter` into `core_fields`/`extended_fields` for cleaner ABC metrics
|
|
32
|
+
- All specs comply with rubocop-rspec conventions
|
|
33
|
+
|
|
34
|
+
## [0.1.1] - 2026-04-23
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- `require 'pathname'` missing in `archive.rb` causing `NameError` in isolation
|
|
39
|
+
- Added regression guard spec for file require isolation
|
|
40
|
+
|
|
41
|
+
|
|
14
42
|
## [0.1.0] - 2026-04-12
|
|
15
43
|
|
|
16
44
|
### Added
|
data/docs/ABBU.md
CHANGED
|
@@ -44,20 +44,39 @@ AddressBook-v22.abcddb
|
|
|
44
44
|
|
|
45
45
|
Key tables:
|
|
46
46
|
|
|
47
|
-
| Table
|
|
48
|
-
|
|
49
|
-
| `ZABCDRECORD`
|
|
50
|
-
| `ZABCDEMAILADDRESS`
|
|
51
|
-
| `ZABCDPHONENUMBER`
|
|
47
|
+
| Table | Purpose |
|
|
48
|
+
|--------------------------|----------------------------------------|
|
|
49
|
+
| `ZABCDRECORD` | One row per contact (name, company) |
|
|
50
|
+
| `ZABCDEMAILADDRESS` | Email addresses (linked by `ZOWNER`) |
|
|
51
|
+
| `ZABCDPHONENUMBER` | Phone numbers (linked by `ZOWNER`) |
|
|
52
|
+
| `ZABCDPOSTALADDRESS` | Street addresses (linked by `ZOWNER`) |
|
|
53
|
+
| `Z_ABCDCONTACTGROUP` | Group membership join table |
|
|
54
|
+
| `ZABCDURLADDRESS` | URLs (linked by `ZOWNER`) |
|
|
55
|
+
| `ZABCDNOTE` | Notes (linked by `ZCONTACT`) |
|
|
56
|
+
| `ZABCDRELATEDNAME` | Related names (linked by `ZOWNER`) |
|
|
57
|
+
| `ZABCDSOCIALPROFILE` | Social profiles (linked by `ZOWNER`) |
|
|
52
58
|
|
|
53
59
|
Notable columns in `ZABCDRECORD`:
|
|
54
60
|
|
|
55
|
-
| Column
|
|
56
|
-
|
|
57
|
-
| `Z_PK`
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
+
| Column | Description |
|
|
62
|
+
|--------------------------|--------------------------|
|
|
63
|
+
| `Z_PK` | Primary key |
|
|
64
|
+
| `Z_ENT` | Entity type (14=contact) |
|
|
65
|
+
| `ZFIRSTNAME` | First name |
|
|
66
|
+
| `ZLASTNAME` | Last name |
|
|
67
|
+
| `ZNICKNAME` | Nickname |
|
|
68
|
+
| `ZTITLE` | Prefix (e.g. "Dr.") |
|
|
69
|
+
| `ZSUFFIX` | Suffix (e.g. "Jr.") |
|
|
70
|
+
| `ZORGANIZATION` | Company / org |
|
|
71
|
+
| `ZJOBTITLE` | Job title |
|
|
72
|
+
| `ZDEPARTMENT` | Department |
|
|
73
|
+
| `ZMAIDENNAME` | Maiden name |
|
|
74
|
+
| `ZPHONETICFIRSTNAME` | Phonetic first name |
|
|
75
|
+
| `ZPHONETICLASTNAME` | Phonetic last name |
|
|
76
|
+
| `ZPHONETICORGANIZATION` | Phonetic company |
|
|
77
|
+
| `ZPRONOUNS` | Pronouns |
|
|
78
|
+
| `ZRINGTONE` | Ringtone |
|
|
79
|
+
| `ZTEXTTONE` | Text tone |
|
|
61
80
|
|
|
62
81
|
### 2. Plist / `.abcdp` (legacy macOS)
|
|
63
82
|
|
data/lib/abbu/archive.rb
CHANGED
data/lib/abbu/contact.rb
CHANGED
|
@@ -3,15 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
module Abbu
|
|
5
5
|
class Contact
|
|
6
|
-
attr_accessor :first_name, :last_name, :emails, :phones, :company
|
|
6
|
+
attr_accessor :first_name, :last_name, :emails, :phones, :company, :addresses, :groups, :nickname, :prefix, :suffix,
|
|
7
|
+
:job_title, :department, :maiden_name, :phonetic_first_name, :phonetic_last_name, :phonetic_company,
|
|
8
|
+
:pronouns, :ringtone, :texttone, :urls, :notes, :related_names, :social_profiles
|
|
7
9
|
|
|
8
10
|
def initialize
|
|
9
11
|
@emails = []
|
|
10
12
|
@phones = []
|
|
13
|
+
@addresses = []
|
|
14
|
+
@groups = []
|
|
15
|
+
@urls = []
|
|
16
|
+
@notes = []
|
|
17
|
+
@related_names = []
|
|
18
|
+
@social_profiles = []
|
|
11
19
|
end
|
|
12
20
|
|
|
13
21
|
def full_name
|
|
14
|
-
|
|
22
|
+
quoted_nickname = nickname ? "\"#{nickname}\"" : nil
|
|
23
|
+
[prefix, first_name, quoted_nickname, last_name, suffix].compact.join(' ')
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
def to_s
|
|
@@ -27,11 +27,45 @@ module Abbu
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def headers
|
|
30
|
-
%w[Name Email Phone Company]
|
|
30
|
+
%w[Name Email Phone Company Address Groups URLs Notes RelatedNames SocialProfiles]
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def row(contact)
|
|
34
|
-
|
|
34
|
+
core_fields(contact) + extended_fields(contact)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def core_fields(contact)
|
|
38
|
+
[
|
|
39
|
+
contact.full_name,
|
|
40
|
+
contact.emails.first&.fetch(:address, nil),
|
|
41
|
+
contact.phones.first&.fetch(:number, nil),
|
|
42
|
+
contact.company,
|
|
43
|
+
format_address(contact.addresses.first),
|
|
44
|
+
contact.groups.join(', ')
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def extended_fields(contact)
|
|
49
|
+
[
|
|
50
|
+
contact.urls.map { |u| u[:url] }.join(', '),
|
|
51
|
+
contact.notes.join("\n"),
|
|
52
|
+
format_related_names(contact.related_names),
|
|
53
|
+
format_social_profiles(contact.social_profiles)
|
|
54
|
+
]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def format_address(addr)
|
|
58
|
+
return nil unless addr
|
|
59
|
+
|
|
60
|
+
[addr[:street], addr[:city], addr[:state], addr[:zip], addr[:country]].compact.join(', ')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def format_related_names(names)
|
|
64
|
+
names.map { |rn| "#{rn[:name]} (#{rn[:label]})" }.join(', ')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def format_social_profiles(profiles)
|
|
68
|
+
profiles.map { |sp| "#{sp[:username]} on #{sp[:service]}" }.join(', ')
|
|
35
69
|
end
|
|
36
70
|
end
|
|
37
71
|
end
|
|
@@ -21,14 +21,29 @@ module Abbu
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
23
|
def payload
|
|
24
|
-
@contacts.map
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
@contacts.map { |c| contact_hash(c) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def contact_hash(contact) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
28
|
+
{
|
|
29
|
+
name: contact.full_name,
|
|
30
|
+
first_name: contact.first_name,
|
|
31
|
+
last_name: contact.last_name,
|
|
32
|
+
nickname: contact.nickname,
|
|
33
|
+
prefix: contact.prefix,
|
|
34
|
+
suffix: contact.suffix,
|
|
35
|
+
company: contact.company,
|
|
36
|
+
job_title: contact.job_title,
|
|
37
|
+
department: contact.department,
|
|
38
|
+
emails: contact.emails,
|
|
39
|
+
phones: contact.phones,
|
|
40
|
+
addresses: contact.addresses,
|
|
41
|
+
groups: contact.groups,
|
|
42
|
+
urls: contact.urls,
|
|
43
|
+
notes: contact.notes,
|
|
44
|
+
related_names: contact.related_names,
|
|
45
|
+
social_profiles: contact.social_profiles
|
|
46
|
+
}.compact
|
|
32
47
|
end
|
|
33
48
|
end
|
|
34
49
|
end
|
|
@@ -22,16 +22,55 @@ module Abbu
|
|
|
22
22
|
@contacts.map { |c| vcard_for(c) }.join("\n")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def vcard_for(contact)
|
|
25
|
+
def vcard_for(contact) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
26
26
|
lines = ['BEGIN:VCARD', 'VERSION:3.0']
|
|
27
27
|
lines << "FN:#{contact.full_name}"
|
|
28
28
|
lines << "N:#{contact.last_name};#{contact.first_name};;;"
|
|
29
|
+
lines << "NICKNAME:#{contact.nickname}" if contact.nickname
|
|
29
30
|
lines << "ORG:#{contact.company}" if contact.company
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
lines << "TITLE:#{contact.job_title}" if contact.job_title
|
|
32
|
+
append_emails(lines, contact)
|
|
33
|
+
append_phones(lines, contact)
|
|
34
|
+
append_addresses(lines, contact)
|
|
35
|
+
append_urls(lines, contact)
|
|
36
|
+
append_social_profiles(lines, contact)
|
|
37
|
+
contact.notes.each { |n| lines << "NOTE:#{n}" }
|
|
32
38
|
lines << 'END:VCARD'
|
|
33
39
|
lines.join("\n")
|
|
34
40
|
end
|
|
41
|
+
|
|
42
|
+
def append_emails(lines, contact)
|
|
43
|
+
contact.emails.each do |e|
|
|
44
|
+
label = e[:label] || 'INTERNET'
|
|
45
|
+
lines << "EMAIL;TYPE=#{label}:#{e[:address]}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def append_phones(lines, contact)
|
|
50
|
+
contact.phones.each do |p|
|
|
51
|
+
label = p[:label] || 'VOICE'
|
|
52
|
+
lines << "TEL;TYPE=#{label}:#{p[:number]}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def append_addresses(lines, contact)
|
|
57
|
+
contact.addresses.each do |a|
|
|
58
|
+
label = a[:label] || 'HOME'
|
|
59
|
+
lines << "ADR;TYPE=#{label}:;;#{a[:street]};#{a[:city]};#{a[:state]};#{a[:zip]};#{a[:country]}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def append_urls(lines, contact)
|
|
64
|
+
contact.urls.each do |u|
|
|
65
|
+
lines << "URL:#{u[:url]}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def append_social_profiles(lines, contact)
|
|
70
|
+
contact.social_profiles.each do |sp|
|
|
71
|
+
lines << "X-SOCIALPROFILE;TYPE=#{sp[:service]}:#{sp[:username]}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
35
74
|
end
|
|
36
75
|
end
|
|
37
76
|
end
|
|
@@ -6,7 +6,21 @@ require_relative '../contact'
|
|
|
6
6
|
|
|
7
7
|
module Abbu
|
|
8
8
|
module Parsers
|
|
9
|
-
class SqliteParser
|
|
9
|
+
class SqliteParser # rubocop:disable Metrics/ClassLength
|
|
10
|
+
# Column-name → attr_accessor mapping for flat fields on ZABCDRECORD
|
|
11
|
+
RECORD_FIELD_MAP = {
|
|
12
|
+
'ZFIRSTNAME' => :first_name, 'ZLASTNAME' => :last_name,
|
|
13
|
+
'ZNICKNAME' => :nickname, 'ZTITLE' => :prefix,
|
|
14
|
+
'ZSUFFIX' => :suffix, 'ZORGANIZATION' => :company,
|
|
15
|
+
'ZJOBTITLE' => :job_title, 'ZDEPARTMENT' => :department,
|
|
16
|
+
'ZMAIDENNAME' => :maiden_name,
|
|
17
|
+
'ZPHONETICFIRSTNAME' => :phonetic_first_name,
|
|
18
|
+
'ZPHONETICLASTNAME' => :phonetic_last_name,
|
|
19
|
+
'ZPHONETICORGANIZATION' => :phonetic_company,
|
|
20
|
+
'ZPRONOUNS' => :pronouns,
|
|
21
|
+
'ZRINGTONE' => :ringtone, 'ZTEXTTONE' => :texttone
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
10
24
|
def initialize(db_paths)
|
|
11
25
|
@db_paths = Array(db_paths)
|
|
12
26
|
end
|
|
@@ -28,32 +42,111 @@ module Abbu
|
|
|
28
42
|
end
|
|
29
43
|
|
|
30
44
|
def records(db)
|
|
31
|
-
|
|
45
|
+
# Exclude groups (typically Z_ENT = 15 in this schema version)
|
|
46
|
+
db.execute('SELECT * FROM ZABCDRECORD WHERE Z_ENT != 15')
|
|
32
47
|
end
|
|
33
48
|
|
|
34
49
|
def emails_for(db, record_id)
|
|
35
50
|
db.execute(
|
|
36
|
-
'SELECT ZADDRESSNORMALIZED FROM ZABCDEMAILADDRESS WHERE ZOWNER = ?',
|
|
51
|
+
'SELECT ZADDRESSNORMALIZED, ZLABEL FROM ZABCDEMAILADDRESS WHERE ZOWNER = ?',
|
|
37
52
|
record_id
|
|
38
|
-
).
|
|
53
|
+
).map { |row| { address: row['ZADDRESSNORMALIZED'], label: row['ZLABEL'] } }
|
|
39
54
|
end
|
|
40
55
|
|
|
41
56
|
def phones_for(db, record_id)
|
|
42
57
|
db.execute(
|
|
43
|
-
'SELECT ZFULLNUMBER FROM ZABCDPHONENUMBER WHERE ZOWNER = ?',
|
|
58
|
+
'SELECT ZFULLNUMBER, ZLABEL FROM ZABCDPHONENUMBER WHERE ZOWNER = ?',
|
|
59
|
+
record_id
|
|
60
|
+
).map { |row| { number: row['ZFULLNUMBER'], label: row['ZLABEL'] } }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def addresses_for(db, record_id) # rubocop:disable Metrics/MethodLength
|
|
64
|
+
db.execute(
|
|
65
|
+
'SELECT ZSTREET, ZCITY, ZSTATE, ZZIPCODE, ZCOUNTRYNAME, ZLABEL FROM ZABCDPOSTALADDRESS WHERE ZOWNER = ?',
|
|
44
66
|
record_id
|
|
45
|
-
).
|
|
67
|
+
).map do |row|
|
|
68
|
+
{
|
|
69
|
+
street: row['ZSTREET'],
|
|
70
|
+
city: row['ZCITY'],
|
|
71
|
+
state: row['ZSTATE'],
|
|
72
|
+
zip: row['ZZIPCODE'],
|
|
73
|
+
country: row['ZCOUNTRYNAME'],
|
|
74
|
+
label: row['ZLABEL']
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def groups_for(db, record_id)
|
|
80
|
+
query = <<-SQL
|
|
81
|
+
SELECT g.ZFIRSTNAME
|
|
82
|
+
FROM Z_ABCDCONTACTGROUP j
|
|
83
|
+
JOIN ZABCDRECORD g ON j.Z_GROUP = g.Z_PK
|
|
84
|
+
WHERE j.Z_CONTACT = ?
|
|
85
|
+
SQL
|
|
86
|
+
db.execute(query, record_id).map { |row| row['ZFIRSTNAME'] }
|
|
87
|
+
rescue SQLite3::SQLException
|
|
88
|
+
[]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def urls_for(db, record_id)
|
|
92
|
+
db.execute(
|
|
93
|
+
'SELECT ZURL, ZLABEL FROM ZABCDURLADDRESS WHERE ZOWNER = ?',
|
|
94
|
+
record_id
|
|
95
|
+
).map { |row| { url: row['ZURL'], label: row['ZLABEL'] } }
|
|
96
|
+
rescue SQLite3::SQLException
|
|
97
|
+
[]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def notes_for(db, record_id)
|
|
101
|
+
db.execute(
|
|
102
|
+
'SELECT ZTEXT FROM ZABCDNOTE WHERE ZCONTACT = ?',
|
|
103
|
+
record_id
|
|
104
|
+
).filter_map { |row| row['ZTEXT'] }
|
|
105
|
+
rescue SQLite3::SQLException
|
|
106
|
+
[]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def related_names_for(db, record_id)
|
|
110
|
+
db.execute(
|
|
111
|
+
'SELECT ZNAME, ZLABEL FROM ZABCDRELATEDNAME WHERE ZOWNER = ?',
|
|
112
|
+
record_id
|
|
113
|
+
).map { |row| { name: row['ZNAME'], label: row['ZLABEL'] } }
|
|
114
|
+
rescue SQLite3::SQLException
|
|
115
|
+
[]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def social_profiles_for(db, record_id)
|
|
119
|
+
db.execute(
|
|
120
|
+
'SELECT ZSERVICENAME, ZUSERNAME FROM ZABCDSOCIALPROFILE WHERE ZOWNER = ?',
|
|
121
|
+
record_id
|
|
122
|
+
).map { |row| { service: row['ZSERVICENAME'], username: row['ZUSERNAME'] } }
|
|
123
|
+
rescue SQLite3::SQLException
|
|
124
|
+
[]
|
|
46
125
|
end
|
|
47
126
|
|
|
48
127
|
def build_contact(db, row)
|
|
49
128
|
contact = Contact.new
|
|
50
|
-
contact
|
|
51
|
-
contact
|
|
52
|
-
contact.company = row['ZORGANIZATION']
|
|
53
|
-
contact.emails = emails_for(db, row['Z_PK'])
|
|
54
|
-
contact.phones = phones_for(db, row['Z_PK'])
|
|
129
|
+
assign_flat_fields(contact, row)
|
|
130
|
+
assign_relational_fields(contact, db, row['Z_PK'])
|
|
55
131
|
contact
|
|
56
132
|
end
|
|
133
|
+
|
|
134
|
+
def assign_flat_fields(contact, row)
|
|
135
|
+
RECORD_FIELD_MAP.each do |column, attr|
|
|
136
|
+
contact.public_send(:"#{attr}=", row[column])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def assign_relational_fields(contact, db, record_id) # rubocop:disable Metrics/AbcSize
|
|
141
|
+
contact.emails = emails_for(db, record_id)
|
|
142
|
+
contact.phones = phones_for(db, record_id)
|
|
143
|
+
contact.addresses = addresses_for(db, record_id)
|
|
144
|
+
contact.groups = groups_for(db, record_id)
|
|
145
|
+
contact.urls = urls_for(db, record_id)
|
|
146
|
+
contact.notes = notes_for(db, record_id)
|
|
147
|
+
contact.related_names = related_names_for(db, record_id)
|
|
148
|
+
contact.social_profiles = social_profiles_for(db, record_id)
|
|
149
|
+
end
|
|
57
150
|
end
|
|
58
151
|
end
|
|
59
152
|
end
|
data/lib/abbu/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: abbu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stan Carver II
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sqlite3
|
|
@@ -150,6 +150,20 @@ dependencies:
|
|
|
150
150
|
- - "~>"
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
152
|
version: '0.6'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: rubocop-rspec
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '3.0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '3.0'
|
|
153
167
|
- !ruby/object:Gem::Dependency
|
|
154
168
|
name: simplecov
|
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|