lutaml-model 0.8.0 → 0.8.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/.github/workflows/dependent-repos.json +9 -0
- data/.github/workflows/downstream-performance.yml +0 -3
- data/.rubocop_todo.yml +18 -186
- data/README.adoc +212 -15
- data/bench/bench_xmi.rb +6 -6
- data/bench/gate_config.rb +2 -9
- data/docs/_pages/configuration.adoc +155 -41
- data/docs/_pages/serialization_adapters.adoc +65 -14
- data/docs/index.adoc +3 -1
- data/docs/yamls_sequence.adoc +335 -0
- data/lib/lutaml/hash_format.rb +4 -0
- data/lib/lutaml/json/adapter/multi_json_adapter.rb +4 -2
- data/lib/lutaml/json/adapter/oj_adapter.rb +4 -2
- data/lib/lutaml/json.rb +4 -0
- data/lib/lutaml/key_value/adapter/json/multi_json_adapter.rb +4 -2
- data/lib/lutaml/key_value/adapter/json/oj_adapter.rb +4 -2
- data/lib/lutaml/model/adapter_resolver.rb +410 -0
- data/lib/lutaml/model/adapter_scope.rb +64 -0
- data/lib/lutaml/model/config.rb +84 -21
- data/lib/lutaml/model/configuration.rb +17 -249
- data/lib/lutaml/model/format_registry.rb +44 -117
- data/lib/lutaml/model/mapping/listener.rb +4 -2
- data/lib/lutaml/model/serialize/format_conversion.rb +42 -3
- data/lib/lutaml/model/serialize.rb +4 -2
- data/lib/lutaml/model/services/base.rb +4 -2
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +2 -0
- data/lib/lutaml/toml.rb +10 -3
- data/lib/lutaml/xml/serialization/instance_methods.rb +6 -0
- data/lib/lutaml/xml.rb +3 -4
- data/lib/lutaml/yaml.rb +4 -0
- data/lib/lutaml/yamls/adapter/mapping.rb +7 -0
- data/lib/lutaml/yamls/adapter/standard_adapter.rb +23 -2
- data/lib/lutaml/yamls/adapter/transform.rb +105 -7
- data/lib/lutaml/yamls/adapter/yamls_sequence.rb +20 -0
- data/lib/lutaml/yamls/adapter/yamls_sequence_rule.rb +48 -0
- data/lib/lutaml/yamls/adapter.rb +2 -0
- data/spec/fixtures/geolexica_v2_concept.rb +136 -0
- data/spec/fixtures/geolexica_v2_sample.yaml +36 -0
- data/spec/fixtures/geolexica_v2_sample2.yaml +38 -0
- data/spec/fixtures/yamls_range_concept.rb +139 -0
- data/spec/lutaml/model/xml_decoupling_spec.rb +5 -4
- data/spec/lutaml/model/yamls_range_spec.rb +393 -0
- data/spec/lutaml/model/yamls_sequence_spec.rb +245 -0
- data/spec/spec_helper.rb +5 -0
- metadata +13 -3
- data/bench/bench_uniword.rb +0 -69
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require_relative "../../lib/lutaml/model"
|
|
2
|
+
|
|
3
|
+
module YamlsRangeTest
|
|
4
|
+
class Header < Lutaml::Model::Serializable
|
|
5
|
+
attribute :title, :string
|
|
6
|
+
attribute :version, :integer
|
|
7
|
+
|
|
8
|
+
yaml do
|
|
9
|
+
map "title", to: :title
|
|
10
|
+
map "version", to: :version
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Metadata < Lutaml::Model::Serializable
|
|
15
|
+
attribute :author, :string
|
|
16
|
+
attribute :date, :string
|
|
17
|
+
|
|
18
|
+
yaml do
|
|
19
|
+
map "author", to: :author
|
|
20
|
+
map "date", to: :date
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Entry < Lutaml::Model::Serializable
|
|
25
|
+
attribute :name, :string
|
|
26
|
+
attribute :value, :string
|
|
27
|
+
|
|
28
|
+
yaml do
|
|
29
|
+
map "name", to: :name
|
|
30
|
+
map "value", to: :value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Footer < Lutaml::Model::Serializable
|
|
35
|
+
attribute :note, :string
|
|
36
|
+
|
|
37
|
+
yaml do
|
|
38
|
+
map "note", to: :note
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Uses various range patterns: 0..1, 2..3, -1, etc.
|
|
43
|
+
class Document < Lutaml::Model::Serializable
|
|
44
|
+
attribute :headers, Header, collection: true
|
|
45
|
+
attribute :entries, Entry, collection: true
|
|
46
|
+
attribute :footer, Footer
|
|
47
|
+
|
|
48
|
+
yamls do
|
|
49
|
+
sequence do
|
|
50
|
+
map_document 0..1, to: :headers, type: Header, collection: true
|
|
51
|
+
map_document 2..3, to: :entries, type: Entry, collection: true
|
|
52
|
+
map_document -1, to: :footer, type: Footer
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Uses negative ranges: -2..-1 for the last 2 docs
|
|
58
|
+
class DocumentNegRange < Lutaml::Model::Serializable
|
|
59
|
+
attribute :headers, Header, collection: true
|
|
60
|
+
attribute :trailers, Entry, collection: true
|
|
61
|
+
|
|
62
|
+
yamls do
|
|
63
|
+
sequence do
|
|
64
|
+
map_document 0..1, to: :headers, type: Header, collection: true
|
|
65
|
+
map_document -2..-1, to: :trailers, type: Entry, collection: true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Uses 2..-1 to capture from position 2 to end
|
|
71
|
+
class DocumentOpenRange < Lutaml::Model::Serializable
|
|
72
|
+
attribute :header, Header
|
|
73
|
+
attribute :rest, Entry, collection: true
|
|
74
|
+
|
|
75
|
+
yamls do
|
|
76
|
+
sequence do
|
|
77
|
+
map_document 0, to: :header, type: Header
|
|
78
|
+
map_document 1..-1, to: :rest, type: Entry, collection: true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Uses -1 for last doc as a collection
|
|
84
|
+
class DocumentLastOnly < Lutaml::Model::Serializable
|
|
85
|
+
attribute :last_entry, Entry
|
|
86
|
+
|
|
87
|
+
yamls do
|
|
88
|
+
sequence do
|
|
89
|
+
map_document -1, to: :last_entry, type: Entry
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 3 ranges with 3 different types:
|
|
95
|
+
# 0..1 → Headers (flex range in front), 2..3 → Metadata, -2..-1 → Footers (flex range at back)
|
|
96
|
+
class ThreeRangesFrontFlex < Lutaml::Model::Serializable
|
|
97
|
+
attribute :headers, Header, collection: true
|
|
98
|
+
attribute :metas, Metadata, collection: true
|
|
99
|
+
attribute :trailers, Entry, collection: true
|
|
100
|
+
|
|
101
|
+
yamls do
|
|
102
|
+
sequence do
|
|
103
|
+
map_document 0..1, to: :headers, type: Header, collection: true
|
|
104
|
+
map_document 2..3, to: :metas, type: Metadata, collection: true
|
|
105
|
+
map_document -2..-1, to: :trailers, type: Entry, collection: true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# 3 ranges: 0 (single Header), 1..3 (Metadata collection), -1 (single Footer)
|
|
111
|
+
class ThreeRangesMixed < Lutaml::Model::Serializable
|
|
112
|
+
attribute :header, Header
|
|
113
|
+
attribute :metas, Metadata, collection: true
|
|
114
|
+
attribute :footer, Footer
|
|
115
|
+
|
|
116
|
+
yamls do
|
|
117
|
+
sequence do
|
|
118
|
+
map_document 0, to: :header, type: Header
|
|
119
|
+
map_document 1..3, to: :metas, type: Metadata, collection: true
|
|
120
|
+
map_document -1, to: :footer, type: Footer
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# 3 ranges: 0..1 (Headers), -3..-2 (Entries), -1 (Footer)
|
|
126
|
+
class ThreeRangesNegMiddle < Lutaml::Model::Serializable
|
|
127
|
+
attribute :headers, Header, collection: true
|
|
128
|
+
attribute :entries, Entry, collection: true
|
|
129
|
+
attribute :footer, Footer
|
|
130
|
+
|
|
131
|
+
yamls do
|
|
132
|
+
sequence do
|
|
133
|
+
map_document 0..1, to: :headers, type: Header, collection: true
|
|
134
|
+
map_document -3..-2, to: :entries, type: Entry, collection: true
|
|
135
|
+
map_document -1, to: :footer, type: Footer
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -97,12 +97,13 @@ RSpec.describe "XML decoupling from core model" do
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
describe "Config" do
|
|
100
|
-
it "does not include :xml in AVAILABLE_FORMATS constant" do
|
|
101
|
-
expect(Lutaml::Model::
|
|
100
|
+
it "does not include :xml in Config::AVAILABLE_FORMATS constant" do
|
|
101
|
+
expect(Lutaml::Model::Config::AVAILABLE_FORMATS).not_to include(:xml)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
it "does not include XML in
|
|
105
|
-
|
|
104
|
+
it "does not include XML in AdapterResolver metadata" do
|
|
105
|
+
# XML is registered dynamically via FormatRegistry, not in Config boot constants
|
|
106
|
+
expect(Lutaml::Model::FormatRegistry.registered?(:xml)).to be true
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
it "allows setting XML adapter via Config" do
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require_relative "../../fixtures/yamls_range_concept"
|
|
3
|
+
|
|
4
|
+
RSpec.describe "YAMLS sequence range positions" do
|
|
5
|
+
# Doc 0-1: Header fields (title/version), Doc 2-3: Entry fields (name/value), Doc 4: Footer fields (note)
|
|
6
|
+
let(:mixed_doc_yaml) do
|
|
7
|
+
<<~YAMLS
|
|
8
|
+
---
|
|
9
|
+
title: Doc Zero
|
|
10
|
+
version: 0
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
title: Doc One
|
|
14
|
+
version: 1
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
name: Doc Two
|
|
18
|
+
value: v2
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
name: Doc Three
|
|
22
|
+
value: v3
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
note: end of stream
|
|
26
|
+
YAMLS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# All docs use Entry fields (name/value) for Entry-only models
|
|
30
|
+
let(:entry_doc_yaml) do
|
|
31
|
+
<<~YAMLS
|
|
32
|
+
---
|
|
33
|
+
name: Doc Zero
|
|
34
|
+
value: v0
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
name: Doc One
|
|
38
|
+
value: v1
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
name: Doc Two
|
|
42
|
+
value: v2
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
name: Doc Three
|
|
46
|
+
value: v3
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
name: Doc Four
|
|
50
|
+
value: v4
|
|
51
|
+
YAMLS
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# All docs use Header fields (title/version) for Header-only models
|
|
55
|
+
let(:header_doc_yaml) do
|
|
56
|
+
<<~YAMLS
|
|
57
|
+
---
|
|
58
|
+
title: Doc Zero
|
|
59
|
+
version: 0
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
title: Doc One
|
|
63
|
+
version: 1
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
title: Doc Two
|
|
67
|
+
version: 2
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
title: Doc Three
|
|
71
|
+
version: 3
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
title: Doc Four
|
|
75
|
+
version: 4
|
|
76
|
+
YAMLS
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# --- 3-range tests with 3 different class types ---
|
|
80
|
+
|
|
81
|
+
# 7-doc YAML: docs 0-1 = Header, docs 2-3 = Metadata, docs 4-5 = Entry, doc 6 = Footer
|
|
82
|
+
let(:seven_doc_yaml) do
|
|
83
|
+
<<~YAMLS
|
|
84
|
+
---
|
|
85
|
+
title: Alpha
|
|
86
|
+
version: 1
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
title: Beta
|
|
90
|
+
version: 2
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
author: Alice
|
|
94
|
+
date: 2024-01-01
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
author: Bob
|
|
98
|
+
date: 2024-06-15
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
name: EntryOne
|
|
102
|
+
value: val1
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
name: EntryTwo
|
|
106
|
+
value: val2
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
note: final note
|
|
110
|
+
YAMLS
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe "range 0..1, 2..3, and negative -1" do
|
|
114
|
+
subject(:doc) { YamlsRangeTest::Document.from_yamls(mixed_doc_yaml) }
|
|
115
|
+
|
|
116
|
+
it "maps documents 0 and 1 to headers collection" do
|
|
117
|
+
expect(doc.headers.length).to eq(2)
|
|
118
|
+
expect(doc.headers[0].title).to eq("Doc Zero")
|
|
119
|
+
expect(doc.headers[0].version).to eq(0)
|
|
120
|
+
expect(doc.headers[1].title).to eq("Doc One")
|
|
121
|
+
expect(doc.headers[1].version).to eq(1)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "maps documents 2 and 3 to entries collection" do
|
|
125
|
+
expect(doc.entries.length).to eq(2)
|
|
126
|
+
expect(doc.entries[0].name).to eq("Doc Two")
|
|
127
|
+
expect(doc.entries[0].value).to eq("v2")
|
|
128
|
+
expect(doc.entries[1].name).to eq("Doc Three")
|
|
129
|
+
expect(doc.entries[1].value).to eq("v3")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "maps document -1 (last) to footer" do
|
|
133
|
+
expect(doc.footer).to be_a(YamlsRangeTest::Footer)
|
|
134
|
+
expect(doc.footer.note).to eq("end of stream")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "round-trips through serialization" do
|
|
138
|
+
output = doc.to_yamls
|
|
139
|
+
doc2 = YamlsRangeTest::Document.from_yamls(output)
|
|
140
|
+
expect(doc2.headers.length).to eq(2)
|
|
141
|
+
expect(doc2.headers[0].title).to eq("Doc Zero")
|
|
142
|
+
expect(doc2.entries.length).to eq(2)
|
|
143
|
+
expect(doc2.entries[0].name).to eq("Doc Two")
|
|
144
|
+
expect(doc2.footer.note).to eq("end of stream")
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe "negative range -2..-1" do
|
|
149
|
+
subject(:doc) { YamlsRangeTest::DocumentNegRange.from_yamls(mixed_doc_yaml) }
|
|
150
|
+
|
|
151
|
+
it "maps documents 0 and 1 to headers" do
|
|
152
|
+
expect(doc.headers.length).to eq(2)
|
|
153
|
+
expect(doc.headers[0].title).to eq("Doc Zero")
|
|
154
|
+
expect(doc.headers[1].title).to eq("Doc One")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "maps documents -2..-1 (docs 3 and 4) to trailers" do
|
|
158
|
+
expect(doc.trailers.length).to eq(2)
|
|
159
|
+
expect(doc.trailers[0].name).to eq("Doc Three")
|
|
160
|
+
expect(doc.trailers[0].value).to eq("v3")
|
|
161
|
+
# Doc 4 has 'note' not 'name'/'value', so Entry fields are nil
|
|
162
|
+
expect(doc.trailers[1].name).to be_nil
|
|
163
|
+
expect(doc.trailers[1].value).to be_nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "round-trips through serialization" do
|
|
167
|
+
output = doc.to_yamls
|
|
168
|
+
doc2 = YamlsRangeTest::DocumentNegRange.from_yamls(output)
|
|
169
|
+
expect(doc2.headers.length).to eq(2)
|
|
170
|
+
expect(doc2.trailers.length).to eq(2)
|
|
171
|
+
expect(doc2.trailers[0].name).to eq("Doc Three")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
describe "range 1..-1 (from position 1 to end)" do
|
|
176
|
+
# Doc 0 is Header (title/version), docs 1-4 are Entry (name/value)
|
|
177
|
+
subject(:doc) { YamlsRangeTest::DocumentOpenRange.from_yamls(open_range_yaml) }
|
|
178
|
+
|
|
179
|
+
let(:open_range_yaml) do
|
|
180
|
+
<<~YAMLS
|
|
181
|
+
---
|
|
182
|
+
title: Doc Zero
|
|
183
|
+
version: 0
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
name: Doc One
|
|
187
|
+
value: v1
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
name: Doc Two
|
|
191
|
+
value: v2
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
name: Doc Three
|
|
195
|
+
value: v3
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
name: Doc Four
|
|
199
|
+
value: v4
|
|
200
|
+
YAMLS
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "maps document 0 to header" do
|
|
204
|
+
expect(doc.header.title).to eq("Doc Zero")
|
|
205
|
+
expect(doc.header.version).to eq(0)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it "maps documents 1..-1 (all remaining) to rest collection" do
|
|
209
|
+
expect(doc.rest.length).to eq(4)
|
|
210
|
+
expect(doc.rest[0].name).to eq("Doc One")
|
|
211
|
+
expect(doc.rest[0].value).to eq("v1")
|
|
212
|
+
expect(doc.rest[1].name).to eq("Doc Two")
|
|
213
|
+
expect(doc.rest[2].name).to eq("Doc Three")
|
|
214
|
+
expect(doc.rest[3].name).to eq("Doc Four")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "round-trips through serialization" do
|
|
218
|
+
output = doc.to_yamls
|
|
219
|
+
doc2 = YamlsRangeTest::DocumentOpenRange.from_yamls(output)
|
|
220
|
+
expect(doc2.header.title).to eq("Doc Zero")
|
|
221
|
+
expect(doc2.rest.length).to eq(4)
|
|
222
|
+
expect(doc2.rest[0].name).to eq("Doc One")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
describe "negative single index -1" do
|
|
227
|
+
subject(:doc) { YamlsRangeTest::DocumentLastOnly.from_yamls(entry_doc_yaml) }
|
|
228
|
+
|
|
229
|
+
it "maps only the last document" do
|
|
230
|
+
expect(doc.last_entry).to be_a(YamlsRangeTest::Entry)
|
|
231
|
+
expect(doc.last_entry.name).to eq("Doc Four")
|
|
232
|
+
expect(doc.last_entry.value).to eq("v4")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "round-trips through serialization" do
|
|
236
|
+
output = doc.to_yamls
|
|
237
|
+
doc2 = YamlsRangeTest::DocumentLastOnly.from_yamls(output)
|
|
238
|
+
expect(doc2.last_entry.name).to eq("Doc Four")
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe "YamlsSequenceRule#resolve_range" do
|
|
243
|
+
let(:rule_class) { Lutaml::Yamls::Adapter::YamlsSequenceRule }
|
|
244
|
+
|
|
245
|
+
it "resolves Integer 0 to 0..0" do
|
|
246
|
+
rule = rule_class.new(0, to: :x, type: Object)
|
|
247
|
+
expect(rule.resolve_range(5)).to eq(0..0)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it "resolves Integer -1 to 4..4 when doc_count is 5" do
|
|
251
|
+
rule = rule_class.new(-1, to: :x, type: Object)
|
|
252
|
+
expect(rule.resolve_range(5)).to eq(4..4)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "resolves Integer -2 to 3..3 when doc_count is 5" do
|
|
256
|
+
rule = rule_class.new(-2, to: :x, type: Object)
|
|
257
|
+
expect(rule.resolve_range(5)).to eq(3..3)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it "resolves Range 0..1 to 0..1" do
|
|
261
|
+
rule = rule_class.new(0..1, to: :x, type: Object)
|
|
262
|
+
expect(rule.resolve_range(5)).to eq(0..1)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it "resolves Range -2..-1 to 3..4 when doc_count is 5" do
|
|
266
|
+
rule = rule_class.new(-2..-1, to: :x, type: Object)
|
|
267
|
+
expect(rule.resolve_range(5)).to eq(3..4)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "resolves Range 1.. to 1..4 when doc_count is 5" do
|
|
271
|
+
rule = rule_class.new(1.., to: :x, type: Object)
|
|
272
|
+
expect(rule.resolve_range(5)).to eq(1..4)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it "resolves Range 2..-1 to 2..4 when doc_count is 5" do
|
|
276
|
+
rule = rule_class.new(2..-1, to: :x, type: Object)
|
|
277
|
+
expect(rule.resolve_range(5)).to eq(2..4)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "resolves Range -3..-1 to 2..4 when doc_count is 5" do
|
|
281
|
+
rule = rule_class.new(-3..-1, to: :x, type: Object)
|
|
282
|
+
expect(rule.resolve_range(5)).to eq(2..4)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it "clamps out-of-bounds end index" do
|
|
286
|
+
rule = rule_class.new(3..10, to: :x, type: Object)
|
|
287
|
+
expect(rule.resolve_range(5)).to eq(3..4)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "clamps negative start that resolves below 0" do
|
|
291
|
+
rule = rule_class.new(-10..-1, to: :x, type: Object)
|
|
292
|
+
expect(rule.resolve_range(5)).to eq(0..4)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it "returns nil for zero doc_count" do
|
|
296
|
+
rule = rule_class.new(0, to: :x, type: Object)
|
|
297
|
+
expect(rule.resolve_range(0)).to be_nil
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
describe "3 ranges: 0..1 (Header), 2..3 (Metadata), -2..-1 (Entry) — flex range at back" do
|
|
302
|
+
subject(:doc) { YamlsRangeTest::ThreeRangesFrontFlex.from_yamls(seven_doc_yaml) }
|
|
303
|
+
|
|
304
|
+
it "maps docs 0..1 to headers" do
|
|
305
|
+
expect(doc.headers.length).to eq(2)
|
|
306
|
+
expect(doc.headers[0].title).to eq("Alpha")
|
|
307
|
+
expect(doc.headers[1].title).to eq("Beta")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it "maps docs 2..3 to metas" do
|
|
311
|
+
expect(doc.metas.length).to eq(2)
|
|
312
|
+
expect(doc.metas[0].author).to eq("Alice")
|
|
313
|
+
expect(doc.metas[1].author).to eq("Bob")
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "maps docs -2..-1 (docs 5 and 6) to trailers" do
|
|
317
|
+
expect(doc.trailers.length).to eq(2)
|
|
318
|
+
expect(doc.trailers[0].name).to eq("EntryTwo")
|
|
319
|
+
expect(doc.trailers[0].value).to eq("val2")
|
|
320
|
+
# doc 6 has 'note' field, not 'name'/'value', so Entry fields are nil
|
|
321
|
+
expect(doc.trailers[1].name).to be_nil
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it "round-trips through serialization" do
|
|
325
|
+
output = doc.to_yamls
|
|
326
|
+
doc2 = YamlsRangeTest::ThreeRangesFrontFlex.from_yamls(output)
|
|
327
|
+
expect(doc2.headers.length).to eq(2)
|
|
328
|
+
expect(doc2.metas.length).to eq(2)
|
|
329
|
+
expect(doc2.trailers.length).to eq(2)
|
|
330
|
+
expect(doc2.metas[0].author).to eq("Alice")
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
describe "3 ranges: 0 (Header), 1..3 (Metadata), -1 (Footer) — mixed single and range" do
|
|
335
|
+
subject(:doc) { YamlsRangeTest::ThreeRangesMixed.from_yamls(seven_doc_yaml) }
|
|
336
|
+
|
|
337
|
+
it "maps doc 0 to header" do
|
|
338
|
+
expect(doc.header.title).to eq("Alpha")
|
|
339
|
+
expect(doc.header.version).to eq(1)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it "maps docs 1..3 to metas" do
|
|
343
|
+
expect(doc.metas.length).to eq(3)
|
|
344
|
+
# doc 1 has Header fields (title/version), mapped as Metadata → author is nil
|
|
345
|
+
expect(doc.metas[0].author).to be_nil
|
|
346
|
+
expect(doc.metas[1].author).to eq("Alice")
|
|
347
|
+
expect(doc.metas[2].author).to eq("Bob")
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it "maps doc -1 (last) to footer" do
|
|
351
|
+
expect(doc.footer.note).to eq("final note")
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it "round-trips through serialization" do
|
|
355
|
+
output = doc.to_yamls
|
|
356
|
+
doc2 = YamlsRangeTest::ThreeRangesMixed.from_yamls(output)
|
|
357
|
+
expect(doc2.header.title).to eq("Alpha")
|
|
358
|
+
expect(doc2.metas.length).to eq(3)
|
|
359
|
+
expect(doc2.footer.note).to eq("final note")
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
describe "3 ranges: 0..1 (Header), -3..-2 (Entry), -1 (Footer) — negative middle range" do
|
|
364
|
+
subject(:doc) { YamlsRangeTest::ThreeRangesNegMiddle.from_yamls(seven_doc_yaml) }
|
|
365
|
+
|
|
366
|
+
it "maps docs 0..1 to headers" do
|
|
367
|
+
expect(doc.headers.length).to eq(2)
|
|
368
|
+
expect(doc.headers[0].title).to eq("Alpha")
|
|
369
|
+
expect(doc.headers[1].title).to eq("Beta")
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
it "maps docs -3..-2 (docs 4 and 5) to entries" do
|
|
373
|
+
expect(doc.entries.length).to eq(2)
|
|
374
|
+
expect(doc.entries[0].name).to eq("EntryOne")
|
|
375
|
+
expect(doc.entries[0].value).to eq("val1")
|
|
376
|
+
expect(doc.entries[1].name).to eq("EntryTwo")
|
|
377
|
+
expect(doc.entries[1].value).to eq("val2")
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
it "maps doc -1 (last) to footer" do
|
|
381
|
+
expect(doc.footer.note).to eq("final note")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it "round-trips through serialization" do
|
|
385
|
+
output = doc.to_yamls
|
|
386
|
+
doc2 = YamlsRangeTest::ThreeRangesNegMiddle.from_yamls(output)
|
|
387
|
+
expect(doc2.headers.length).to eq(2)
|
|
388
|
+
expect(doc2.entries.length).to eq(2)
|
|
389
|
+
expect(doc2.entries[0].name).to eq("EntryOne")
|
|
390
|
+
expect(doc2.footer.note).to eq("final note")
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|