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.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +27 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.rubocop_todo.yml +450 -0
  7. data/CLAUDE.md +57 -0
  8. data/CODE_OF_CONDUCT.md +132 -0
  9. data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
  10. data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
  11. data/Gemfile +15 -0
  12. data/Gemfile.lock +220 -0
  13. data/README.adoc +1430 -0
  14. data/Rakefile +12 -0
  15. data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
  16. data/TODO.impl/1-lutaml-hal-migration.md +60 -0
  17. data/TODO.impl/2-glossarist-migration.md +359 -0
  18. data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
  19. data/bin/console +11 -0
  20. data/bin/setup +8 -0
  21. data/demo/Gemfile +15 -0
  22. data/demo/Gemfile.lock +61 -0
  23. data/demo/README.adoc +301 -0
  24. data/demo/data/vcards/co/contact_10_thompson.data +1 -0
  25. data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
  26. data/demo/data/vcards/co/contact_1_doe.data +1 -0
  27. data/demo/data/vcards/co/contact_1_doe.meta +1 -0
  28. data/demo/data/vcards/co/contact_2_smith.data +1 -0
  29. data/demo/data/vcards/co/contact_2_smith.meta +1 -0
  30. data/demo/data/vcards/co/contact_3_johnson.data +1 -0
  31. data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
  32. data/demo/data/vcards/co/contact_4_garcia.data +1 -0
  33. data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
  34. data/demo/data/vcards/co/contact_5_wilson.data +1 -0
  35. data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
  36. data/demo/data/vcards/co/contact_6_brown.data +1 -0
  37. data/demo/data/vcards/co/contact_6_brown.meta +1 -0
  38. data/demo/data/vcards/co/contact_7_davis.data +1 -0
  39. data/demo/data/vcards/co/contact_7_davis.meta +1 -0
  40. data/demo/data/vcards/co/contact_8_anderson.data +1 -0
  41. data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
  42. data/demo/data/vcards/co/contact_9_taylor.data +1 -0
  43. data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
  44. data/demo/data/vcards.db +0 -0
  45. data/demo/pottery_class_demo.rb +164 -0
  46. data/demo/vcard_models.rb +140 -0
  47. data/demo/vcard_store_demo.rb +526 -0
  48. data/lib/lutaml/store/adapter/base.rb +65 -0
  49. data/lib/lutaml/store/adapter/filesystem.rb +288 -0
  50. data/lib/lutaml/store/adapter/memory.rb +225 -0
  51. data/lib/lutaml/store/adapter/sqlite.rb +193 -0
  52. data/lib/lutaml/store/adapter.rb +12 -0
  53. data/lib/lutaml/store/attribute_updater.rb +198 -0
  54. data/lib/lutaml/store/basic_store.rb +190 -0
  55. data/lib/lutaml/store/cache.rb +108 -0
  56. data/lib/lutaml/store/cache_store.rb +282 -0
  57. data/lib/lutaml/store/composite_model_handler.rb +169 -0
  58. data/lib/lutaml/store/compression.rb +137 -0
  59. data/lib/lutaml/store/config.rb +178 -0
  60. data/lib/lutaml/store/database_store.rb +425 -0
  61. data/lib/lutaml/store/events.rb +92 -0
  62. data/lib/lutaml/store/format/base.rb +33 -0
  63. data/lib/lutaml/store/format/json.rb +25 -0
  64. data/lib/lutaml/store/format/jsonl.rb +37 -0
  65. data/lib/lutaml/store/format/marshal_format.rb +37 -0
  66. data/lib/lutaml/store/format/yaml.rb +29 -0
  67. data/lib/lutaml/store/format/yamls.rb +35 -0
  68. data/lib/lutaml/store/format.rb +33 -0
  69. data/lib/lutaml/store/http_cache.rb +279 -0
  70. data/lib/lutaml/store/http_cache_config.rb +53 -0
  71. data/lib/lutaml/store/http_cache_entry.rb +69 -0
  72. data/lib/lutaml/store/http_header_processor.rb +175 -0
  73. data/lib/lutaml/store/integrity.rb +102 -0
  74. data/lib/lutaml/store/model_registration.rb +75 -0
  75. data/lib/lutaml/store/model_registry.rb +123 -0
  76. data/lib/lutaml/store/model_serializer.rb +69 -0
  77. data/lib/lutaml/store/monitor.rb +192 -0
  78. data/lib/lutaml/store/storage_key.rb +40 -0
  79. data/lib/lutaml/store/version.rb +7 -0
  80. data/lib/lutaml/store.rb +41 -0
  81. data/lutaml-store.gemspec +35 -0
  82. data/plan.adoc +606 -0
  83. data/sig/lutaml/store.rbs +6 -0
  84. data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
  85. data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
  86. data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
  87. data/spec/lutaml/store/autoload_spec.rb +34 -0
  88. data/spec/lutaml/store/cache_store_spec.rb +271 -0
  89. data/spec/lutaml/store/compression_spec.rb +78 -0
  90. data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
  91. data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
  92. data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
  93. data/spec/lutaml/store/database_store_spec.rb +279 -0
  94. data/spec/lutaml/store/file_io_spec.rb +219 -0
  95. data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
  96. data/spec/lutaml/store/format_spec.rb +70 -0
  97. data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
  98. data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
  99. data/spec/lutaml/store/http_cache_spec.rb +422 -0
  100. data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
  101. data/spec/lutaml/store/import_spec.rb +90 -0
  102. data/spec/lutaml/store/integrity_spec.rb +157 -0
  103. data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
  104. data/spec/lutaml/store/load_save_spec.rb +107 -0
  105. data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
  106. data/spec/lutaml/store/model_serializer_spec.rb +140 -0
  107. data/spec/lutaml/store/store_spec.rb +182 -0
  108. data/spec/lutaml/store_spec.rb +21 -0
  109. data/spec/spec_helper.rb +16 -0
  110. 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