lutaml-store 0.2.0 → 0.2.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +13 -177
  3. data/CLAUDE.md +22 -9
  4. data/README.adoc +326 -1103
  5. data/lib/lutaml/store/adapter/base.rb +4 -0
  6. data/lib/lutaml/store/adapter/memory.rb +8 -0
  7. data/lib/lutaml/store/adapter.rb +17 -0
  8. data/lib/lutaml/store/basic_store.rb +1 -10
  9. data/lib/lutaml/store/cache_store.rb +11 -16
  10. data/lib/lutaml/store/database_store.rb +19 -9
  11. data/lib/lutaml/store/format/base.rb +4 -0
  12. data/lib/lutaml/store/format/marshal_format.rb +4 -0
  13. data/lib/lutaml/store/format/xml.rb +37 -0
  14. data/lib/lutaml/store/format/yamls.rb +1 -1
  15. data/lib/lutaml/store/format.rb +22 -1
  16. data/lib/lutaml/store/format_serializer.rb +44 -0
  17. data/lib/lutaml/store/http_cache.rb +3 -13
  18. data/lib/lutaml/store/model_registration.rb +5 -2
  19. data/lib/lutaml/store/model_registry.rb +22 -20
  20. data/lib/lutaml/store/package_store.rb +2 -18
  21. data/lib/lutaml/store/package_transport/base.rb +60 -0
  22. data/lib/lutaml/store/package_transport/directory_transport.rb +196 -0
  23. data/lib/lutaml/store/package_transport/zip_transport.rb +183 -0
  24. data/lib/lutaml/store/package_transport.rb +11 -438
  25. data/lib/lutaml/store/storage_key.rb +1 -1
  26. data/lib/lutaml/store/version.rb +1 -1
  27. data/lib/lutaml/store.rb +1 -0
  28. metadata +14 -77
  29. data/.github/workflows/main.yml +0 -27
  30. data/.gitignore +0 -12
  31. data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +0 -209
  32. data/CORRECTED_HTTP_CACHE_PLAN.md +0 -164
  33. data/Gemfile +0 -15
  34. data/Gemfile.lock +0 -227
  35. data/TODO.impl/0-lutaml-store-self-quality.md +0 -112
  36. data/TODO.impl/1-lutaml-hal-migration.md +0 -96
  37. data/TODO.impl/2-glossarist-migration.md +0 -359
  38. data/TODO.impl/3-lutaml-jsonschema-migration.md +0 -273
  39. data/bin/console +0 -11
  40. data/bin/setup +0 -8
  41. data/demo/Gemfile +0 -15
  42. data/demo/Gemfile.lock +0 -61
  43. data/demo/README.adoc +0 -301
  44. data/demo/data/vcards/co/contact_10_thompson.data +0 -1
  45. data/demo/data/vcards/co/contact_10_thompson.meta +0 -1
  46. data/demo/data/vcards/co/contact_1_doe.data +0 -1
  47. data/demo/data/vcards/co/contact_1_doe.meta +0 -1
  48. data/demo/data/vcards/co/contact_2_smith.data +0 -1
  49. data/demo/data/vcards/co/contact_2_smith.meta +0 -1
  50. data/demo/data/vcards/co/contact_3_johnson.data +0 -1
  51. data/demo/data/vcards/co/contact_3_johnson.meta +0 -1
  52. data/demo/data/vcards/co/contact_4_garcia.data +0 -1
  53. data/demo/data/vcards/co/contact_4_garcia.meta +0 -1
  54. data/demo/data/vcards/co/contact_5_wilson.data +0 -1
  55. data/demo/data/vcards/co/contact_5_wilson.meta +0 -1
  56. data/demo/data/vcards/co/contact_6_brown.data +0 -1
  57. data/demo/data/vcards/co/contact_6_brown.meta +0 -1
  58. data/demo/data/vcards/co/contact_7_davis.data +0 -1
  59. data/demo/data/vcards/co/contact_7_davis.meta +0 -1
  60. data/demo/data/vcards/co/contact_8_anderson.data +0 -1
  61. data/demo/data/vcards/co/contact_8_anderson.meta +0 -1
  62. data/demo/data/vcards/co/contact_9_taylor.data +0 -1
  63. data/demo/data/vcards/co/contact_9_taylor.meta +0 -1
  64. data/demo/data/vcards.db +0 -0
  65. data/demo/pottery_class_demo.rb +0 -164
  66. data/demo/vcard_models.rb +0 -140
  67. data/demo/vcard_store_demo.rb +0 -526
  68. data/lutaml-store.gemspec +0 -36
  69. data/plan.adoc +0 -606
  70. data/spec/lutaml/store/adapter_interface_spec.rb +0 -89
  71. data/spec/lutaml/store/anti_pattern_guard_spec.rb +0 -35
  72. data/spec/lutaml/store/anti_pattern_spec.rb +0 -78
  73. data/spec/lutaml/store/autoload_spec.rb +0 -34
  74. data/spec/lutaml/store/cache_store_spec.rb +0 -271
  75. data/spec/lutaml/store/compression_spec.rb +0 -78
  76. data/spec/lutaml/store/config_enhanced_spec.rb +0 -158
  77. data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +0 -336
  78. data/spec/lutaml/store/custom_serializer_spec.rb +0 -108
  79. data/spec/lutaml/store/database_store_spec.rb +0 -279
  80. data/spec/lutaml/store/file_io_spec.rb +0 -220
  81. data/spec/lutaml/store/format/yamls_spec.rb +0 -80
  82. data/spec/lutaml/store/format_round_trip_spec.rb +0 -110
  83. data/spec/lutaml/store/format_spec.rb +0 -70
  84. data/spec/lutaml/store/http_cache_entry_spec.rb +0 -203
  85. data/spec/lutaml/store/http_cache_hal_integration_spec.rb +0 -404
  86. data/spec/lutaml/store/http_cache_spec.rb +0 -422
  87. data/spec/lutaml/store/http_header_processor_spec.rb +0 -290
  88. data/spec/lutaml/store/import_spec.rb +0 -90
  89. data/spec/lutaml/store/integrity_spec.rb +0 -157
  90. data/spec/lutaml/store/key_collision_serializer_spec.rb +0 -98
  91. data/spec/lutaml/store/load_save_spec.rb +0 -107
  92. data/spec/lutaml/store/lutaml_model_integration_spec.rb +0 -291
  93. data/spec/lutaml/store/model_serializer_spec.rb +0 -140
  94. data/spec/lutaml/store/package_definition_spec.rb +0 -89
  95. data/spec/lutaml/store/package_store_spec.rb +0 -153
  96. data/spec/lutaml/store/package_transport/directory_transport_spec.rb +0 -139
  97. data/spec/lutaml/store/package_transport/zip_transport_spec.rb +0 -85
  98. data/spec/lutaml/store/store_spec.rb +0 -182
  99. data/spec/lutaml/store_spec.rb +0 -21
  100. data/spec/spec_helper.rb +0 -16
  101. data/spec/support/simple_test_model.rb +0 -15
  102. data/spec/support/yamls_test_model.rb +0 -35
@@ -28,6 +28,10 @@ module Lutaml
28
28
  raise NotImplementedError
29
29
  end
30
30
 
31
+ def each_key(&block)
32
+ raise NotImplementedError
33
+ end
34
+
31
35
  def all
32
36
  raise NotImplementedError
33
37
  end
@@ -93,6 +93,14 @@ module Lutaml
93
93
  end
94
94
  end
95
95
 
96
+ def each_key(&block)
97
+ current_keys = @mutex.synchronize do
98
+ cleanup_expired if @ttl_enabled
99
+ @data.keys
100
+ end
101
+ current_keys.each(&block)
102
+ end
103
+
96
104
  def close
97
105
  @mutex.synchronize do
98
106
  @data.clear
@@ -7,6 +7,23 @@ module Lutaml
7
7
  autoload :Memory, "lutaml/store/adapter/memory"
8
8
  autoload :FileSystem, "lutaml/store/adapter/filesystem"
9
9
  autoload :Sqlite, "lutaml/store/adapter/sqlite"
10
+
11
+ @registry = {
12
+ memory: "Memory",
13
+ filesystem: "FileSystem",
14
+ sqlite: "Sqlite"
15
+ }
16
+
17
+ def self.resolve(type, options = {})
18
+ entry = @registry[type.to_sym]
19
+ raise ConfigurationError, "Unknown adapter type: #{type}" unless entry
20
+
21
+ const_get(entry).new(options)
22
+ end
23
+
24
+ def self.register(type, adapter_class)
25
+ @registry[type.to_sym] = adapter_class.name
26
+ end
10
27
  end
11
28
  end
12
29
  end
@@ -167,16 +167,7 @@ module Lutaml
167
167
  end
168
168
 
169
169
  def create_adapter
170
- case @config.adapter_type
171
- when :memory
172
- Adapter::Memory.new(@config.adapter_options)
173
- when :filesystem
174
- Adapter::FileSystem.new(@config.adapter_options)
175
- when :sqlite
176
- Adapter::Sqlite.new(@config.adapter_options)
177
- else
178
- raise ConfigurationError, "Unknown adapter type: #{@config.adapter_type}"
179
- end
170
+ Adapter.resolve(@config.adapter_type, @config.adapter_options)
180
171
  end
181
172
 
182
173
  def create_cache
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "time"
4
5
 
5
6
  module Lutaml
6
7
  module Store
@@ -222,15 +223,18 @@ module Lutaml
222
223
  end
223
224
  end
224
225
 
225
- def fetch(key, ttl: :default, metadata: {}, &block)
226
+ def fetch(key, default = nil, ttl: :default, metadata: {})
226
227
  value = get(key)
227
228
  return value unless value.nil?
228
229
 
229
- return nil unless block_given?
230
-
231
- value = block.call
232
- set(key, value, ttl: ttl, metadata: metadata)
233
- value
230
+ if block_given?
231
+ value = yield
232
+ set(key, value, ttl: ttl, metadata: metadata)
233
+ value
234
+ elsif !default.nil?
235
+ set(key, default, ttl: ttl, metadata: metadata)
236
+ default
237
+ end
234
238
  end
235
239
 
236
240
  def close
@@ -243,16 +247,7 @@ module Lutaml
243
247
  adapter_type = config[:adapter]&.dig(:type) || config[:adapter_type] || :memory
244
248
  adapter_options = config[:adapter]&.dig(:options) || config[:adapter_options] || {}
245
249
 
246
- case adapter_type.to_sym
247
- when :memory
248
- Adapter::Memory.new(adapter_options)
249
- when :filesystem
250
- Adapter::FileSystem.new(adapter_options)
251
- when :sqlite
252
- Adapter::Sqlite.new(adapter_options)
253
- else
254
- raise ConfigurationError, "Unknown adapter type: #{adapter_type}"
255
- end
250
+ Adapter.resolve(adapter_type, adapter_options)
256
251
  end
257
252
 
258
253
  def serialize_entry(entry)
@@ -207,7 +207,7 @@ module Lutaml
207
207
 
208
208
  content = fmt.serialize_many(models_array)
209
209
 
210
- File.write(path, content, encoding: "utf-8")
210
+ write_file(path, content, fmt)
211
211
  @store.emit_event(:model_export, count: models_array.size, path: path)
212
212
  path
213
213
  end
@@ -331,8 +331,8 @@ module Lutaml
331
331
  Dir.glob(glob).sort.each do |file_path|
332
332
  next unless File.file?(file_path)
333
333
 
334
- raw = File.read(file_path, encoding: "utf-8")
335
- next if raw.strip.empty?
334
+ raw = read_file(file_path, fmt)
335
+ next if !fmt.binary? && raw.strip.empty?
336
336
 
337
337
  begin
338
338
  model = fmt.deserialize(raw, model_class)
@@ -351,8 +351,8 @@ module Lutaml
351
351
  Dir.glob(glob).sort.each do |file_path|
352
352
  next unless File.file?(file_path)
353
353
 
354
- raw = File.read(file_path, encoding: "utf-8")
355
- next if raw.strip.empty?
354
+ raw = read_file(file_path, fmt)
355
+ next if !fmt.binary? && raw.strip.empty?
356
356
 
357
357
  begin
358
358
  loaded = fmt.deserialize_many(raw, model_class)
@@ -375,8 +375,7 @@ module Lutaml
375
375
  key = extract_model_key(model)
376
376
  filename = key || model.class.name.to_s.gsub("::", "_")
377
377
  file_path = File.join(dir, "#{filename}#{fmt.extension}")
378
- content = fmt.serialize(model)
379
- File.write(file_path, content, encoding: "utf-8")
378
+ write_file(file_path, fmt.serialize(model), fmt)
380
379
  model
381
380
  end
382
381
  end
@@ -391,8 +390,7 @@ module Lutaml
391
390
 
392
391
  grouped.map do |key, group|
393
392
  file_path = File.join(dir, "#{key}#{fmt.extension}")
394
- content = fmt.serialize_many(group)
395
- File.write(file_path, content, encoding: "utf-8")
393
+ write_file(file_path, fmt.serialize_many(group), fmt)
396
394
  group
397
395
  end.flatten
398
396
  end
@@ -420,6 +418,18 @@ module Lutaml
420
418
  basename = File.basename(file_path, ".*")
421
419
  model.public_send(:"#{registration.key_field}=", basename)
422
420
  end
421
+
422
+ def read_file(file_path, fmt)
423
+ fmt.binary? ? File.binread(file_path) : File.read(file_path, encoding: "utf-8")
424
+ end
425
+
426
+ def write_file(file_path, content, fmt)
427
+ if fmt.binary?
428
+ File.binwrite(file_path, content)
429
+ else
430
+ File.write(file_path, content, encoding: "utf-8")
431
+ end
432
+ end
423
433
  end
424
434
  end
425
435
  end
@@ -27,6 +27,10 @@ module Lutaml
27
27
  def deserialize_many(_data, _model_class)
28
28
  raise NotImplementedError, "#{self.class} does not support multi-document deserialization"
29
29
  end
30
+
31
+ def binary?
32
+ false
33
+ end
30
34
  end
31
35
  end
32
36
  end
@@ -31,6 +31,10 @@ module Lutaml
31
31
  hash_array = ::Marshal.load(data)
32
32
  hash_array.map { |h| model_class.from_hash(h) }
33
33
  end
34
+
35
+ def binary?
36
+ true
37
+ end
34
38
  end
35
39
  end
36
40
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Store
5
+ module Format
6
+ class Xml < Base
7
+ def extension
8
+ ".xml"
9
+ end
10
+
11
+ def glob_pattern
12
+ "*.xml"
13
+ end
14
+
15
+ def serialize(model)
16
+ model.to_xml
17
+ end
18
+
19
+ def deserialize(data, model_class)
20
+ model_class.from_xml(data)
21
+ end
22
+
23
+ def serialize_many(models)
24
+ inner = models.map(&:to_xml).join("\n")
25
+ "<items>\n#{inner}\n</items>"
26
+ end
27
+
28
+ def deserialize_many(data, model_class)
29
+ doc = Moxml.parse(data)
30
+ doc.root.children.select(&:element?).map do |child|
31
+ model_class.from_xml(child.to_xml)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -17,7 +17,7 @@ module Lutaml
17
17
  end
18
18
 
19
19
  def serialize_many(models)
20
- models.map(&:to_yamls).join
20
+ models.map(&:to_yamls).join("\n")
21
21
  end
22
22
 
23
23
  def deserialize(data, model_class)
@@ -13,13 +13,15 @@ module Lutaml
13
13
  autoload :Json, "lutaml/store/format/json"
14
14
  autoload :Jsonl, "lutaml/store/format/jsonl"
15
15
  autoload :MarshalFormat, "lutaml/store/format/marshal_format"
16
+ autoload :Xml, "lutaml/store/format/xml"
16
17
 
17
18
  FORMATS = {
18
19
  yaml: "Yaml",
19
20
  yamls: "Yamls",
20
21
  json: "Json",
21
22
  jsonl: "Jsonl",
22
- marshal: "MarshalFormat"
23
+ marshal: "MarshalFormat",
24
+ xml: "Xml"
23
25
  }.freeze
24
26
 
25
27
  def self.resolve(format)
@@ -28,6 +30,25 @@ module Lutaml
28
30
 
29
31
  const_get(entry).new
30
32
  end
33
+
34
+ def self.for_extension(ext)
35
+ extension_map[ext] || extension_map[".#{ext.to_s.sub(/\A\./, "")}"]
36
+ end
37
+
38
+ private_class_method def self.extension_map
39
+ @extension_map ||= begin
40
+ map = {}
41
+ FORMATS.each_value do |class_name|
42
+ fmt = const_get(class_name).new
43
+ ext = fmt.extension
44
+ next if map.key?(ext)
45
+
46
+ map[ext] = fmt
47
+ map[".yml"] = fmt if ext == ".yaml"
48
+ end
49
+ map.freeze
50
+ end
51
+ end
31
52
  end
32
53
  end
33
54
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Store
5
+ # Bridges a Format handler to the ModelSerializer interface.
6
+ # Enables DatabaseStore to use any format (yaml, json, xml, yamls, marshal)
7
+ # for key-value storage instead of the default hash serialization.
8
+ #
9
+ # Usage:
10
+ # serializer = FormatSerializer.new(:yamls)
11
+ # store = DatabaseStore.new(
12
+ # adapter: :sqlite,
13
+ # models: [{ model: ConceptDocument, key: :id, serializer: serializer }]
14
+ # )
15
+ class FormatSerializer
16
+ DATA_KEY = "_data"
17
+ CLASS_KEY = "_class"
18
+
19
+ def initialize(format)
20
+ @format = Format.resolve(format)
21
+ end
22
+
23
+ def serialize(model)
24
+ {
25
+ DATA_KEY => @format.serialize(model),
26
+ CLASS_KEY => model.class.name
27
+ }
28
+ end
29
+
30
+ def deserialize(data, model_class)
31
+ model_class = resolve_class(data[CLASS_KEY]) if data[CLASS_KEY]
32
+ @format.deserialize(data[DATA_KEY], model_class)
33
+ end
34
+
35
+ private
36
+
37
+ def resolve_class(class_name)
38
+ Object.const_get(class_name)
39
+ rescue NameError
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Lutaml
6
4
  module Store
7
5
  class HttpCache
@@ -145,9 +143,7 @@ module Lutaml
145
143
  data = @adapter.get(key)
146
144
  next unless data
147
145
 
148
- entry_data = data.is_a?(String) ? JSON.parse(data) : data
149
- entry = HttpCacheEntry.from_hash(entry_data)
150
- entries << entry
146
+ entries << HttpCacheEntry.from_json(data)
151
147
  rescue StandardError
152
148
  # Skip invalid entries
153
149
  end
@@ -174,20 +170,14 @@ module Lutaml
174
170
  data = @adapter.get(cache_key)
175
171
  return nil unless data
176
172
 
177
- # Handle both serialized and hash data
178
- entry_data = data.is_a?(String) ? JSON.parse(data) : data
179
- HttpCacheEntry.from_hash(entry_data)
173
+ HttpCacheEntry.from_json(data)
180
174
  rescue StandardError
181
- # Log error and continue without cache
182
175
  nil
183
176
  end
184
177
 
185
178
  def store_entry(cache_key, entry)
186
- # Serialize entry for storage
187
- data = entry.to_hash
188
- @adapter.set(cache_key, data)
179
+ @adapter.set(cache_key, entry.to_json)
189
180
  rescue StandardError
190
- # Log error but don't fail the request
191
181
  false
192
182
  end
193
183
 
@@ -4,14 +4,17 @@ module Lutaml
4
4
  module Store
5
5
  # Represents a single model registration with its metadata
6
6
  class ModelRegistration
7
- attr_reader :model_class, :key_field, :polymorphic_class_key, :serializer, :dir
7
+ attr_reader :model_class, :key_field, :polymorphic_class_key, :serializer, :dir,
8
+ :composites
8
9
 
9
- def initialize(model_class, key_field, polymorphic_class_key: nil, serializer: nil, dir: nil)
10
+ def initialize(model_class, key_field, polymorphic_class_key: nil, serializer: nil,
11
+ dir: nil, composites: [])
10
12
  @model_class = model_class
11
13
  @key_field = key_field.to_sym
12
14
  @polymorphic_class_key = polymorphic_class_key&.to_sym
13
15
  @serializer = serializer
14
16
  @dir = dir
17
+ @composites = composites.map(&:to_sym)
15
18
  validate!
16
19
  end
17
20
 
@@ -81,34 +81,36 @@ module Lutaml
81
81
  @registrations.clear
82
82
  end
83
83
 
84
- # Find models that are registered and nested within other models
84
+ # Find declared composite models nested within the given model
85
85
  def find_composite_models(model)
86
- composite_models = {}
86
+ registration = find_registration(model.class)
87
+ return {} unless registration&.composites&.any?
87
88
 
88
- model.class.attributes.each_key do |attr_name|
89
- attr_value = model.public_send(attr_name)
90
- next if attr_value.nil?
91
-
92
- if attr_value.is_a?(Object) && registered?(attr_value.class)
93
- add_composite_entry(composite_models, attr_name,
94
- attr_value)
95
- end
89
+ registration.composites.each_with_object({}) do |attr_name, result|
90
+ collect_composite(model, attr_name, result)
91
+ end
92
+ end
96
93
 
97
- next unless attr_value.is_a?(Array)
94
+ private
98
95
 
99
- attr_value.each_with_index do |item, index|
100
- next unless item.is_a?(Object) && registered?(item.class)
96
+ def collect_composite(model, attr_name, result)
97
+ attr_value = model.public_send(attr_name)
98
+ return if attr_value.nil?
101
99
 
102
- add_composite_entry(composite_models, "#{attr_name}.#{index}", item)
103
- end
100
+ if attr_value.is_a?(Array)
101
+ collect_array_composites(attr_value, attr_name, result)
102
+ elsif registered?(attr_value.class)
103
+ add_composite_entry(result, attr_name, attr_value)
104
104
  end
105
-
106
- composite_models
107
- rescue NoMethodError
108
- {}
109
105
  end
110
106
 
111
- private
107
+ def collect_array_composites(values, attr_name, result)
108
+ values.each_with_index do |item, index|
109
+ next unless registered?(item.class)
110
+
111
+ add_composite_entry(result, "#{attr_name}.#{index}", item)
112
+ end
113
+ end
112
114
 
113
115
  def add_composite_entry(composite_models, attr_path, model_instance)
114
116
  registration = find_registration(model_instance.class)
@@ -20,15 +20,13 @@ module Lutaml
20
20
 
21
21
  def self.load(definition, path, transport: :directory, format: nil)
22
22
  store = new(definition)
23
- transporter = resolve_transport(transport)
24
- transporter.read(path, store, format: format)
23
+ PackageTransport.resolve(transport).read(path, store, format: format)
25
24
  store
26
25
  end
27
26
 
28
27
  def save(path, transport: :directory, format: nil, formats: {})
29
28
  resolved_formats = resolve_formats(format, formats)
30
- transporter = self.class.resolve_transport(transport)
31
- transporter.write(path, self, formats: resolved_formats)
29
+ PackageTransport.resolve(transport).write(path, self, formats: resolved_formats)
32
30
  end
33
31
 
34
32
  # ── Model CRUD ──
@@ -118,22 +116,8 @@ module Lutaml
118
116
  }
119
117
  end
120
118
 
121
- # Public access for PackageTransport (avoids instance_variable_get).
122
- attr_reader :db
123
-
124
119
  private
125
120
 
126
- def self.resolve_transport(transport)
127
- case transport
128
- when :directory, "directory"
129
- PackageTransport::DirectoryTransport.new
130
- when :zip, "zip"
131
- PackageTransport::ZipTransport.new
132
- else
133
- raise ConfigurationError, "Unknown transport: #{transport}"
134
- end
135
- end
136
-
137
121
  def resolve_formats(global_format, per_model_formats)
138
122
  if global_format
139
123
  definition.model_entries
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Lutaml
6
+ module Store
7
+ module PackageTransport
8
+ class Base
9
+ def read(path, package_store, format: nil)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def write(path, package_store, formats: {})
14
+ raise NotImplementedError
15
+ end
16
+
17
+ private
18
+
19
+ def resolve_format(format_name)
20
+ Format.resolve(format_name)
21
+ end
22
+
23
+ def effective_format(entry, formats)
24
+ formats[entry.model] || entry.default_format
25
+ end
26
+
27
+ def format_for_file(filename)
28
+ Format.for_extension(File.extname(filename)) || Format.resolve(:yaml)
29
+ end
30
+
31
+ def extract_key(model, entry)
32
+ model.public_send(entry.key).to_s
33
+ end
34
+
35
+ def set_key_from_filename(model, filename, entry)
36
+ return if model.public_send(entry.key)
37
+
38
+ basename = File.basename(filename, ".*")
39
+ model.public_send(:"#{entry.key}=", basename)
40
+ end
41
+
42
+ def sanitize_filename(key)
43
+ key.gsub(%r{[/:#?]}, "_")
44
+ end
45
+
46
+ def read_file(file_path, fmt)
47
+ fmt.binary? ? File.binread(file_path) : File.read(file_path, encoding: "utf-8")
48
+ end
49
+
50
+ def write_file(file_path, content, fmt)
51
+ if fmt.binary?
52
+ File.binwrite(file_path, content)
53
+ else
54
+ File.write(file_path, content, encoding: "utf-8")
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end