lutaml-store 0.1.1
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 +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +450 -0
- data/CLAUDE.md +57 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
- data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +220 -0
- data/README.adoc +1430 -0
- data/Rakefile +12 -0
- data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
- data/TODO.impl/1-lutaml-hal-migration.md +60 -0
- data/TODO.impl/2-glossarist-migration.md +359 -0
- data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/demo/Gemfile +15 -0
- data/demo/Gemfile.lock +61 -0
- data/demo/README.adoc +301 -0
- data/demo/data/vcards/co/contact_10_thompson.data +1 -0
- data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
- data/demo/data/vcards/co/contact_1_doe.data +1 -0
- data/demo/data/vcards/co/contact_1_doe.meta +1 -0
- data/demo/data/vcards/co/contact_2_smith.data +1 -0
- data/demo/data/vcards/co/contact_2_smith.meta +1 -0
- data/demo/data/vcards/co/contact_3_johnson.data +1 -0
- data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
- data/demo/data/vcards/co/contact_4_garcia.data +1 -0
- data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
- data/demo/data/vcards/co/contact_5_wilson.data +1 -0
- data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
- data/demo/data/vcards/co/contact_6_brown.data +1 -0
- data/demo/data/vcards/co/contact_6_brown.meta +1 -0
- data/demo/data/vcards/co/contact_7_davis.data +1 -0
- data/demo/data/vcards/co/contact_7_davis.meta +1 -0
- data/demo/data/vcards/co/contact_8_anderson.data +1 -0
- data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
- data/demo/data/vcards/co/contact_9_taylor.data +1 -0
- data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
- data/demo/data/vcards.db +0 -0
- data/demo/pottery_class_demo.rb +164 -0
- data/demo/vcard_models.rb +140 -0
- data/demo/vcard_store_demo.rb +526 -0
- data/lib/lutaml/store/adapter/base.rb +65 -0
- data/lib/lutaml/store/adapter/filesystem.rb +288 -0
- data/lib/lutaml/store/adapter/memory.rb +225 -0
- data/lib/lutaml/store/adapter/sqlite.rb +193 -0
- data/lib/lutaml/store/adapter.rb +12 -0
- data/lib/lutaml/store/attribute_updater.rb +198 -0
- data/lib/lutaml/store/basic_store.rb +190 -0
- data/lib/lutaml/store/cache.rb +108 -0
- data/lib/lutaml/store/cache_store.rb +282 -0
- data/lib/lutaml/store/composite_model_handler.rb +169 -0
- data/lib/lutaml/store/compression.rb +137 -0
- data/lib/lutaml/store/config.rb +178 -0
- data/lib/lutaml/store/database_store.rb +425 -0
- data/lib/lutaml/store/events.rb +92 -0
- data/lib/lutaml/store/format/base.rb +33 -0
- data/lib/lutaml/store/format/json.rb +25 -0
- data/lib/lutaml/store/format/jsonl.rb +37 -0
- data/lib/lutaml/store/format/marshal_format.rb +37 -0
- data/lib/lutaml/store/format/yaml.rb +29 -0
- data/lib/lutaml/store/format/yamls.rb +35 -0
- data/lib/lutaml/store/format.rb +33 -0
- data/lib/lutaml/store/http_cache.rb +279 -0
- data/lib/lutaml/store/http_cache_config.rb +53 -0
- data/lib/lutaml/store/http_cache_entry.rb +69 -0
- data/lib/lutaml/store/http_header_processor.rb +175 -0
- data/lib/lutaml/store/integrity.rb +102 -0
- data/lib/lutaml/store/model_registration.rb +75 -0
- data/lib/lutaml/store/model_registry.rb +123 -0
- data/lib/lutaml/store/model_serializer.rb +69 -0
- data/lib/lutaml/store/monitor.rb +192 -0
- data/lib/lutaml/store/storage_key.rb +40 -0
- data/lib/lutaml/store/version.rb +7 -0
- data/lib/lutaml/store.rb +41 -0
- data/lutaml-store.gemspec +35 -0
- data/plan.adoc +606 -0
- data/sig/lutaml/store.rbs +6 -0
- data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
- data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
- data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
- data/spec/lutaml/store/autoload_spec.rb +34 -0
- data/spec/lutaml/store/cache_store_spec.rb +271 -0
- data/spec/lutaml/store/compression_spec.rb +78 -0
- data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
- data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
- data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
- data/spec/lutaml/store/database_store_spec.rb +279 -0
- data/spec/lutaml/store/file_io_spec.rb +219 -0
- data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
- data/spec/lutaml/store/format_spec.rb +70 -0
- data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
- data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
- data/spec/lutaml/store/http_cache_spec.rb +422 -0
- data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
- data/spec/lutaml/store/import_spec.rb +90 -0
- data/spec/lutaml/store/integrity_spec.rb +157 -0
- data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
- data/spec/lutaml/store/load_save_spec.rb +107 -0
- data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
- data/spec/lutaml/store/model_serializer_spec.rb +140 -0
- data/spec/lutaml/store/store_spec.rb +182 -0
- data/spec/lutaml/store_spec.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- metadata +166 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "lutaml/store"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
require "date"
|
|
8
|
+
require "paint"
|
|
9
|
+
require "table_tennis"
|
|
10
|
+
require_relative "vcard_models"
|
|
11
|
+
|
|
12
|
+
# VCard Store Demo
|
|
13
|
+
# This demo showcases the lutaml-store functionality using vCard models
|
|
14
|
+
class VCardStoreDemo
|
|
15
|
+
include VCardDemo
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@stores = {}
|
|
19
|
+
setup_stores
|
|
20
|
+
puts "šÆ VCard Store Demo Initialized"
|
|
21
|
+
puts "=" * 50
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run
|
|
25
|
+
puts "\nš Running VCard Store Demo..."
|
|
26
|
+
|
|
27
|
+
# Create sample vCards
|
|
28
|
+
create_sample_vcards
|
|
29
|
+
|
|
30
|
+
# Demonstrate different storage backends
|
|
31
|
+
demonstrate_memory_store
|
|
32
|
+
demonstrate_filesystem_store
|
|
33
|
+
demonstrate_sqlite_store
|
|
34
|
+
demonstrate_cache_store
|
|
35
|
+
|
|
36
|
+
# Demonstrate search and retrieval
|
|
37
|
+
demonstrate_search_capabilities
|
|
38
|
+
|
|
39
|
+
# Demonstrate data integrity
|
|
40
|
+
demonstrate_integrity_features
|
|
41
|
+
|
|
42
|
+
puts "\nā
Demo completed successfully!"
|
|
43
|
+
puts "Check the demo/data/ directory for persisted files."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def setup_stores
|
|
49
|
+
# Memory store for fast operations
|
|
50
|
+
@stores[:memory] = Lutaml::Store::Store.new(
|
|
51
|
+
adapter: { type: :memory }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Filesystem store for persistence
|
|
55
|
+
@stores[:filesystem] = Lutaml::Store::Store.new(
|
|
56
|
+
adapter: {
|
|
57
|
+
type: :filesystem,
|
|
58
|
+
options: { path: "demo/data/vcards" }
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# SQLite store for structured queries
|
|
63
|
+
@stores[:sqlite] = Lutaml::Store::Store.new(
|
|
64
|
+
adapter: {
|
|
65
|
+
type: :sqlite,
|
|
66
|
+
options: { path: "demo/data/vcards.db" }
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Cache store for performance
|
|
71
|
+
@stores[:cache] = Lutaml::Store::CacheStore.new(
|
|
72
|
+
adapter: { type: :memory },
|
|
73
|
+
default_ttl: 300, # 5 minutes
|
|
74
|
+
max_size: 100
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def create_sample_vcards
|
|
79
|
+
puts "\nš Creating 10 sample vCards..."
|
|
80
|
+
|
|
81
|
+
@sample_vcards = [
|
|
82
|
+
create_vcard(
|
|
83
|
+
fn: "John Doe",
|
|
84
|
+
given: "John", family: "Doe",
|
|
85
|
+
email: ["john.doe@example.com", "j.doe@work.com"],
|
|
86
|
+
phone: ["+1-555-0101", "work"],
|
|
87
|
+
org: "Tech Corp",
|
|
88
|
+
birthday: "1985-03-15"
|
|
89
|
+
),
|
|
90
|
+
create_vcard(
|
|
91
|
+
fn: "Jane Smith",
|
|
92
|
+
given: "Jane", family: "Smith",
|
|
93
|
+
email: ["jane.smith@example.com"],
|
|
94
|
+
phone: ["+1-555-0102", "mobile"],
|
|
95
|
+
org: "Design Studio",
|
|
96
|
+
birthday: "1990-07-22"
|
|
97
|
+
),
|
|
98
|
+
create_vcard(
|
|
99
|
+
fn: "Dr. Robert Johnson",
|
|
100
|
+
prefix: "Dr.", given: "Robert", family: "Johnson",
|
|
101
|
+
email: ["r.johnson@hospital.com", "robert@personal.com"],
|
|
102
|
+
phone: ["+1-555-0103", "work"],
|
|
103
|
+
org: "City Hospital",
|
|
104
|
+
birthday: "1975-11-08"
|
|
105
|
+
),
|
|
106
|
+
create_vcard(
|
|
107
|
+
fn: "Maria Garcia",
|
|
108
|
+
given: "Maria", family: "Garcia",
|
|
109
|
+
email: ["maria.garcia@startup.com"],
|
|
110
|
+
phone: ["+1-555-0104", "mobile"],
|
|
111
|
+
org: "Innovation Labs",
|
|
112
|
+
birthday: "1988-02-14"
|
|
113
|
+
),
|
|
114
|
+
create_vcard(
|
|
115
|
+
fn: "David Wilson",
|
|
116
|
+
given: "David", family: "Wilson",
|
|
117
|
+
email: ["david.wilson@consulting.com"],
|
|
118
|
+
phone: ["+1-555-0105", "work"],
|
|
119
|
+
org: "Wilson Consulting",
|
|
120
|
+
birthday: "1982-09-30"
|
|
121
|
+
),
|
|
122
|
+
create_vcard(
|
|
123
|
+
fn: "Sarah Brown",
|
|
124
|
+
given: "Sarah", family: "Brown",
|
|
125
|
+
email: ["sarah.brown@university.edu", "s.brown@research.org"],
|
|
126
|
+
phone: ["+1-555-0106", "work"],
|
|
127
|
+
org: "State University",
|
|
128
|
+
birthday: "1979-12-03"
|
|
129
|
+
),
|
|
130
|
+
create_vcard(
|
|
131
|
+
fn: "Michael Davis",
|
|
132
|
+
given: "Michael", family: "Davis",
|
|
133
|
+
email: ["mike.davis@agency.com"],
|
|
134
|
+
phone: ["+1-555-0107", "mobile"],
|
|
135
|
+
org: "Creative Agency",
|
|
136
|
+
birthday: "1992-05-18"
|
|
137
|
+
),
|
|
138
|
+
create_vcard(
|
|
139
|
+
fn: "Lisa Anderson",
|
|
140
|
+
given: "Lisa", family: "Anderson",
|
|
141
|
+
email: ["lisa.anderson@finance.com"],
|
|
142
|
+
phone: ["+1-555-0108", "work"],
|
|
143
|
+
org: "Financial Services Inc",
|
|
144
|
+
birthday: "1986-08-25"
|
|
145
|
+
),
|
|
146
|
+
create_vcard(
|
|
147
|
+
fn: "James Taylor",
|
|
148
|
+
given: "James", family: "Taylor",
|
|
149
|
+
email: ["james.taylor@music.com", "jtaylor@personal.net"],
|
|
150
|
+
phone: ["+1-555-0109", "mobile"],
|
|
151
|
+
org: "Music Production",
|
|
152
|
+
birthday: "1983-01-12"
|
|
153
|
+
),
|
|
154
|
+
create_vcard(
|
|
155
|
+
fn: "Emma Thompson",
|
|
156
|
+
given: "Emma", family: "Thompson",
|
|
157
|
+
email: ["emma.thompson@law.com"],
|
|
158
|
+
phone: ["+1-555-0110", "work"],
|
|
159
|
+
org: "Thompson & Associates",
|
|
160
|
+
birthday: "1977-04-07"
|
|
161
|
+
)
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
puts "ā
Created #{@sample_vcards.length} vCards"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def create_vcard(fn:, given:, family:, email:, phone:, org:, birthday:, prefix: nil)
|
|
168
|
+
name = VcardName.new(
|
|
169
|
+
given: given,
|
|
170
|
+
family: family,
|
|
171
|
+
prefix: prefix
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
tel = VcardTel.new(
|
|
175
|
+
value: phone[0],
|
|
176
|
+
type: phone[1]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
bday = VcardBday.new(
|
|
180
|
+
value: birthday
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
Vcard.new(
|
|
184
|
+
fn: fn,
|
|
185
|
+
n: name,
|
|
186
|
+
email: email,
|
|
187
|
+
tel: [tel],
|
|
188
|
+
org: org,
|
|
189
|
+
bday: bday
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def demonstrate_memory_store
|
|
194
|
+
puts "\nš§ Memory Store Demo"
|
|
195
|
+
puts "-" * 30
|
|
196
|
+
|
|
197
|
+
store = @stores[:memory]
|
|
198
|
+
|
|
199
|
+
# Store all vCards
|
|
200
|
+
@sample_vcards.each do |vcard|
|
|
201
|
+
store.set(vcard.uid, vcard.to_h)
|
|
202
|
+
puts "š¾ Stored: #{vcard.fn}"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
puts "š Total stored: #{store.size} vCards"
|
|
206
|
+
|
|
207
|
+
# Retrieve a specific vCard
|
|
208
|
+
first_uid = @sample_vcards.first.uid
|
|
209
|
+
retrieved = store.get(first_uid)
|
|
210
|
+
puts "š Retrieved: #{retrieved["fn"]} by UID"
|
|
211
|
+
|
|
212
|
+
# List all keys
|
|
213
|
+
puts "šļø All UIDs: #{store.keys.length} entries"
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def demonstrate_filesystem_store
|
|
217
|
+
puts "\nš¾ Filesystem Store Demo"
|
|
218
|
+
puts "-" * 30
|
|
219
|
+
|
|
220
|
+
store = @stores[:filesystem]
|
|
221
|
+
|
|
222
|
+
# Store vCards with organized keys
|
|
223
|
+
@sample_vcards.each_with_index do |vcard, index|
|
|
224
|
+
key = "contact_#{index + 1}_#{vcard.n.family.downcase}"
|
|
225
|
+
store.set(key, vcard.to_h)
|
|
226
|
+
puts "š Saved to file: #{key}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
puts "š Total files: #{store.size}"
|
|
230
|
+
|
|
231
|
+
# Demonstrate file persistence
|
|
232
|
+
puts "š½ Files are persisted in: demo/data/vcards/"
|
|
233
|
+
|
|
234
|
+
# Retrieve by organized key
|
|
235
|
+
key = "contact_1_doe"
|
|
236
|
+
return unless store.exists?(key)
|
|
237
|
+
|
|
238
|
+
retrieved = store.get(key)
|
|
239
|
+
puts "š Retrieved from file: #{retrieved["fn"]}"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def demonstrate_sqlite_store
|
|
243
|
+
puts "\nšļø SQLite Store Demo"
|
|
244
|
+
puts "-" * 30
|
|
245
|
+
|
|
246
|
+
store = @stores[:sqlite]
|
|
247
|
+
|
|
248
|
+
# Store with email-based keys for easy lookup
|
|
249
|
+
@sample_vcards.each do |vcard|
|
|
250
|
+
email_key = vcard.primary_email.split("@").first
|
|
251
|
+
store.set(email_key, vcard.to_h)
|
|
252
|
+
puts "šļø Stored in DB: #{email_key} -> #{vcard.fn}"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
puts "š Total DB records: #{store.size}"
|
|
256
|
+
|
|
257
|
+
# Demonstrate database persistence
|
|
258
|
+
puts "š¾ Data persisted in: demo/data/vcards.db"
|
|
259
|
+
|
|
260
|
+
# Retrieve by email key
|
|
261
|
+
retrieved = store.get("john.doe")
|
|
262
|
+
puts "š Retrieved from DB: #{retrieved["fn"]}" if retrieved
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def demonstrate_cache_store
|
|
266
|
+
puts "\nā” Cache Store Demo"
|
|
267
|
+
puts "-" * 30
|
|
268
|
+
|
|
269
|
+
cache = @stores[:cache]
|
|
270
|
+
|
|
271
|
+
# Cache frequently accessed vCards
|
|
272
|
+
@sample_vcards.first(5).each do |vcard|
|
|
273
|
+
cache.set(vcard.uid, vcard.to_h, ttl: 60) # 1 minute TTL
|
|
274
|
+
puts "ā” Cached: #{vcard.fn} (TTL: 60s)"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Demonstrate cache operations
|
|
278
|
+
first_uid = @sample_vcards.first.uid
|
|
279
|
+
puts "š Cache hit: #{cache.get(first_uid)["fn"]}"
|
|
280
|
+
puts "ā±ļø TTL remaining: #{cache.ttl(first_uid).round(2)}s"
|
|
281
|
+
|
|
282
|
+
# Cache statistics
|
|
283
|
+
info = cache.cache_info
|
|
284
|
+
puts "š Cache stats: #{info[:valid_entries]} valid, #{info[:expired_entries]} expired"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def demonstrate_search_capabilities
|
|
288
|
+
puts "\nš Search & Retrieval Demo"
|
|
289
|
+
puts "-" * 30
|
|
290
|
+
|
|
291
|
+
# Search across different stores
|
|
292
|
+
puts "š¢ Finding contacts by organization:"
|
|
293
|
+
find_by_organization("Tech Corp")
|
|
294
|
+
|
|
295
|
+
puts "\nš§ Finding contacts by email domain:"
|
|
296
|
+
find_by_email_domain("example.com")
|
|
297
|
+
|
|
298
|
+
puts "\nš Finding contacts by birth year:"
|
|
299
|
+
find_by_birth_year(1985)
|
|
300
|
+
|
|
301
|
+
# Display contacts in a formatted table
|
|
302
|
+
puts "\nš Contact Directory (Table View)"
|
|
303
|
+
puts "-" * 40
|
|
304
|
+
display_contacts_table
|
|
305
|
+
|
|
306
|
+
# Demonstrate ID-based search
|
|
307
|
+
puts "\nš Search by ID Demo"
|
|
308
|
+
puts "-" * 25
|
|
309
|
+
first_uid = @sample_vcards.first.uid
|
|
310
|
+
colored_search_by_id(first_uid)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def find_by_organization(org_name)
|
|
314
|
+
found = 0
|
|
315
|
+
@stores[:memory].each_key do |key|
|
|
316
|
+
vcard_data = @stores[:memory].get(key)
|
|
317
|
+
if vcard_data["org"] == org_name
|
|
318
|
+
puts " š¤ #{vcard_data["fn"]} - #{vcard_data["org"]}"
|
|
319
|
+
found += 1
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
puts " š Found #{found} contacts in #{org_name}" if found.positive?
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def find_by_email_domain(domain)
|
|
326
|
+
@stores[:filesystem].each_key do |key|
|
|
327
|
+
vcard_data = @stores[:filesystem].get(key)
|
|
328
|
+
emails = vcard_data["email"] || []
|
|
329
|
+
puts " š§ #{vcard_data["fn"]} - #{emails.first}" if emails.any? { |email| email.include?(domain) }
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def find_by_birth_year(year)
|
|
334
|
+
@stores[:sqlite].each_key do |key|
|
|
335
|
+
vcard_data = @stores[:sqlite].get(key)
|
|
336
|
+
puts " š #{vcard_data["fn"]} - born in #{year}" if vcard_data.dig("bday", "value")&.start_with?(year.to_s)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def demonstrate_integrity_features
|
|
341
|
+
puts "\nš Data Integrity Demo"
|
|
342
|
+
puts "-" * 30
|
|
343
|
+
|
|
344
|
+
# Create a store with integrity checking
|
|
345
|
+
integrity_store = Lutaml::Store::Store.new(
|
|
346
|
+
adapter: { type: :memory },
|
|
347
|
+
integrity: { enabled: true, algorithm: :sha256 }
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Store a vCard with integrity checking
|
|
351
|
+
vcard = @sample_vcards.first
|
|
352
|
+
integrity_store.set(vcard.uid, vcard.to_h)
|
|
353
|
+
puts "š Stored with integrity check: #{vcard.fn}"
|
|
354
|
+
|
|
355
|
+
# Verify integrity
|
|
356
|
+
puts "ā
Integrity verified for: #{vcard.fn}" if integrity_store.exists?(vcard.uid)
|
|
357
|
+
|
|
358
|
+
# Demonstrate compression
|
|
359
|
+
compressed_store = Lutaml::Store::Store.new(
|
|
360
|
+
adapter: { type: :memory },
|
|
361
|
+
compression: { enabled: true, algorithm: :gzip }
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
compressed_store.set(vcard.uid, vcard.to_h)
|
|
365
|
+
puts "šļø Stored with compression: #{vcard.fn}"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Display contacts in a formatted table using table_tennis
|
|
369
|
+
def display_contacts_table
|
|
370
|
+
# Prepare data for the table (without colors for table_tennis)
|
|
371
|
+
table_data = []
|
|
372
|
+
|
|
373
|
+
# Header row (plain text for table_tennis)
|
|
374
|
+
table_data << %w[Name Organization Email Phone Birthday]
|
|
375
|
+
|
|
376
|
+
# Data rows from memory store
|
|
377
|
+
@stores[:memory].keys.first(5).each do |key|
|
|
378
|
+
vcard_data = @stores[:memory].get(key)
|
|
379
|
+
emails = vcard_data["email"] || []
|
|
380
|
+
tel_data = vcard_data["tel"] || []
|
|
381
|
+
phone = tel_data.first&.dig("value") || "N/A"
|
|
382
|
+
birthday = vcard_data.dig("bday", "value") || "N/A"
|
|
383
|
+
|
|
384
|
+
table_data << [
|
|
385
|
+
vcard_data["fn"],
|
|
386
|
+
vcard_data["org"] || "N/A",
|
|
387
|
+
emails.first || "N/A",
|
|
388
|
+
phone,
|
|
389
|
+
birthday
|
|
390
|
+
]
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Create and display the table
|
|
394
|
+
table = TableTennis::Table.new(table_data)
|
|
395
|
+
puts table
|
|
396
|
+
|
|
397
|
+
# Show the saved files
|
|
398
|
+
puts "\nš Saved Files in Filesystem Store"
|
|
399
|
+
puts "-" * 40
|
|
400
|
+
show_saved_files
|
|
401
|
+
|
|
402
|
+
# Display individual contact cards
|
|
403
|
+
puts "\nšØ Individual Contact Cards"
|
|
404
|
+
puts "-" * 40
|
|
405
|
+
@sample_vcards.first(3).each do |vcard|
|
|
406
|
+
display_colored_vcard(vcard)
|
|
407
|
+
puts
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Show the actual saved files and their contents
|
|
412
|
+
def show_saved_files
|
|
413
|
+
data_dir = "demo/data/vcards"
|
|
414
|
+
puts "š Directory: #{File.expand_path(data_dir)}"
|
|
415
|
+
|
|
416
|
+
if Dir.exist?(data_dir)
|
|
417
|
+
# Look for .data files specifically
|
|
418
|
+
files = Dir.glob("#{data_dir}/**/*.data").first(3)
|
|
419
|
+
files.each do |file|
|
|
420
|
+
puts "\nš File: #{File.basename(file)}"
|
|
421
|
+
puts " Path: #{file}"
|
|
422
|
+
puts " Content preview:"
|
|
423
|
+
if File.file?(file)
|
|
424
|
+
content = File.read(file)
|
|
425
|
+
# Show first few lines of YAML content
|
|
426
|
+
lines = content.lines.first(8)
|
|
427
|
+
lines.each { |line| puts " #{line.chomp}" }
|
|
428
|
+
puts " ..." if content.lines.length > 8
|
|
429
|
+
else
|
|
430
|
+
puts " ā Not a regular file"
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Also show directory structure
|
|
435
|
+
puts "\nš Directory Structure:"
|
|
436
|
+
Dir.glob("#{data_dir}/**/*").first(10).each do |path|
|
|
437
|
+
relative_path = path.sub("#{data_dir}/", "")
|
|
438
|
+
if File.directory?(path)
|
|
439
|
+
puts " š #{relative_path}/"
|
|
440
|
+
else
|
|
441
|
+
puts " š #{relative_path}"
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
else
|
|
445
|
+
puts "ā Directory not found: #{data_dir}"
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Display a single vCard with colors and formatting
|
|
450
|
+
def display_colored_vcard(vcard)
|
|
451
|
+
puts Paint["ā#{"ā" * 50}ā", :blue, :bold]
|
|
452
|
+
puts Paint["ā", :blue, :bold] + Paint[" #{vcard.fn.center(48)} ", :white, :bold] + Paint["ā", :blue, :bold]
|
|
453
|
+
puts Paint["ā #{"ā" * 50}ā£", :blue, :bold]
|
|
454
|
+
|
|
455
|
+
# Name details
|
|
456
|
+
if vcard.n.prefix
|
|
457
|
+
puts Paint["ā", :blue,
|
|
458
|
+
:bold] + Paint[" Title: ", :cyan,
|
|
459
|
+
:bold] + Paint["#{vcard.n.prefix.ljust(41)} ", :white] + Paint["ā", :blue, :bold]
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
puts Paint["ā", :blue,
|
|
463
|
+
:bold] + Paint[" Given: ", :cyan,
|
|
464
|
+
:bold] + Paint["#{vcard.n.given.ljust(40)} ", :white] + Paint["ā", :blue, :bold]
|
|
465
|
+
puts Paint["ā", :blue,
|
|
466
|
+
:bold] + Paint[" Family: ", :cyan,
|
|
467
|
+
:bold] + Paint["#{vcard.n.family.ljust(39)} ", :white] + Paint["ā", :blue, :bold]
|
|
468
|
+
|
|
469
|
+
# Organization
|
|
470
|
+
puts Paint["ā", :blue,
|
|
471
|
+
:bold] + Paint[" Organization: ", :green,
|
|
472
|
+
:bold] + Paint["#{vcard.org.ljust(33)} ", :green] + Paint["ā", :blue, :bold]
|
|
473
|
+
|
|
474
|
+
# Contact info
|
|
475
|
+
puts Paint["ā", :blue,
|
|
476
|
+
:bold] + Paint[" Email: ", :yellow,
|
|
477
|
+
:bold] + Paint["#{vcard.primary_email.ljust(39)} ",
|
|
478
|
+
:yellow] + Paint["ā", :blue, :bold]
|
|
479
|
+
|
|
480
|
+
if vcard.tel && !vcard.tel.empty?
|
|
481
|
+
tel = vcard.tel.first
|
|
482
|
+
phone_info = "#{tel.value} (#{tel.type})"
|
|
483
|
+
puts Paint["ā", :blue,
|
|
484
|
+
:bold] + Paint[" Phone: ", :magenta,
|
|
485
|
+
:bold] + Paint["#{phone_info.ljust(39)} ", :magenta] + Paint["ā", :blue, :bold]
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Birthday
|
|
489
|
+
if vcard.bday
|
|
490
|
+
puts Paint["ā", :blue,
|
|
491
|
+
:bold] + Paint[" Birthday: ", :cyan,
|
|
492
|
+
:bold] + Paint["#{vcard.bday.value.ljust(37)} ", :cyan] + Paint["ā", :blue, :bold]
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# UID
|
|
496
|
+
puts Paint["ā", :blue,
|
|
497
|
+
:bold] + Paint[" UID: ", :red,
|
|
498
|
+
:bold] + Paint["#{vcard.uid[0..41]}... ", :red] + Paint["ā", :blue, :bold]
|
|
499
|
+
|
|
500
|
+
puts Paint["ā#{"ā" * 50}ā", :blue, :bold]
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Enhanced search with colored output
|
|
504
|
+
def colored_search_by_id(uid)
|
|
505
|
+
puts Paint["\nš Searching for UID: #{uid}", :yellow, :bold]
|
|
506
|
+
|
|
507
|
+
@stores.each do |store_name, store|
|
|
508
|
+
next unless store.exists?(uid)
|
|
509
|
+
|
|
510
|
+
vcard_data = store.get(uid)
|
|
511
|
+
puts Paint["ā
Found in #{store_name} store:", :green, :bold]
|
|
512
|
+
puts Paint[" Name: #{vcard_data["fn"]}", :white]
|
|
513
|
+
puts Paint[" Organization: #{vcard_data["org"]}", :green]
|
|
514
|
+
return vcard_data
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
puts Paint["ā Not found in any store", :red, :bold]
|
|
518
|
+
nil
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Run the demo
|
|
523
|
+
if __FILE__ == $PROGRAM_NAME
|
|
524
|
+
demo = VCardStoreDemo.new
|
|
525
|
+
demo.run
|
|
526
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Store
|
|
5
|
+
module Adapter
|
|
6
|
+
class Base
|
|
7
|
+
def initialize(config = {})
|
|
8
|
+
@config = config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(key)
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set(key, value)
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete(key)
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def exists?(key)
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def keys
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def all
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def clear
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def size
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def close
|
|
44
|
+
# Optional ā override in subclasses that hold resources
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def bulk_get(keys)
|
|
48
|
+
keys.each_with_object({}) { |k, h| h[k] = get(k) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def bulk_set(key_value_pairs)
|
|
52
|
+
key_value_pairs.each { |k, v| set(k, v) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def bulk_delete(keys)
|
|
56
|
+
keys.each_with_object({}) { |k, h| h[k] = delete(k) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def stats
|
|
60
|
+
{ adapter: self.class.name }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|