adf_builder 0.4.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 838eda978e92f15cfa05a3da07e8a80ee1348f07fb4511abcf2ae79063e0917d
4
- data.tar.gz: 205da1b11de1d2748107a0696477787ba0a8f5971ffaabf46f1c25a13fb8079c
3
+ metadata.gz: ded72167a643d102957459b6d439bbaae8f01833303a3eca082ce4a6f0e264dd
4
+ data.tar.gz: 64486e697bd4feefa5fdbc8c6cbe01d3d0492878225748ffdf3c8ed1650b8b09
5
5
  SHA512:
6
- metadata.gz: f85f57a637fcdd0e71b84afda8ad59476a2ffe7ffed04ca521f306950469a87ce895eb71339d1ab48c1fa6d9b5ebab26699654ce9556c613deb39291e876ad72
7
- data.tar.gz: bdade6f04b5a64dbb042202052c3ba174f8263b00d735e858a1f9627ce25e4524a53b41d42200108258914c2e582efbcd8f0bf449e17a0de2fd741cd37b489dd
6
+ metadata.gz: b868564c56972b958839382c2d5c5706ce1b7864ea13e2c7eb38dddccb82f6481a66bc471f679f79589533a8fa9fd29897a387d53d4426ee8bc5490242912b45
7
+ data.tar.gz: 0c25a32fec2c3015e402fdbe26e0c2af23a2a91ea64840097a7a86acd7c88c4e89a809362b1ea35b8eebaa112dc130534b82f1a3647173ab5f0d1f3aa2b19fa0
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  .rspec_status
12
12
 
13
13
  deploy.txt
14
+ *.gem
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- ## [Unreleased]
1
+ ## [1.1.0] - 2026-01-19
2
+ - **Feature Complete**: Implemented all ADF 1.0 nodes and attributes including `Vendor`, `Provider`, and complex `Vehicle` tags (`Finance`, `Option`, `Odometer`, `ColorCombination`, `ImageTag`, `Price`).
3
+ - **Singular Field logic**: Methods for singular fields (e.g. `vehicle.year`) now correctly replace existing values instead of appending.
4
+ - **Removed Legacy Code**: Cleaned up deprecated legacy implementation directories.
5
+
6
+ ## [1.0.0] - 2026-01-19
7
+ - **MAJOR OVERHAUL**: Complete rewrtie of the library architecture.
8
+ - **New Block-based DSL**: Intuitive API for building ADF documents (`AdfBuilder.build { vehicle { ... } }`).
9
+ - **Validation**: Strict enforcement of ADF enumerations and structure (e.g. `vehicle status: :new`).
10
+ - **Editing**: New `AdfBuilder.tree` method for programmatic modifications after construction.
11
+ - **Robustness**: Complete rewrite of XML generation using robust heuristics and strict `Ox` serialization.
12
+ - **Compatibility**: Verified for Ruby 3.4.x.
13
+ - **Features**:
14
+ - Support for multiple vehicles/customers.
15
+ - Support for singular vs multiple item logic.
16
+ - Dynamic support for arbitrary/custom tags (`method_missing`).
17
+ - Automatic handling of XML attributes vs simple elements.
2
18
 
3
19
  ## [0.4.0] - 2026-01-19
4
20
  - Modernized dependencies
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- adf_builder (0.4.0)
4
+ adf_builder (1.1.0)
5
5
  ox (~> 2.14)
6
6
 
7
7
  GEM
@@ -13,6 +13,8 @@ GEM
13
13
  json (2.18.0)
14
14
  language_server-protocol (3.17.0.5)
15
15
  lint_roller (1.1.0)
16
+ nokogiri (1.19.0-arm64-darwin)
17
+ racc (~> 1.4)
16
18
  ox (2.14.23)
17
19
  bigdecimal (>= 3.0)
18
20
  parallel (1.27.0)
@@ -62,6 +64,7 @@ PLATFORMS
62
64
 
63
65
  DEPENDENCIES
64
66
  adf_builder!
67
+ nokogiri (~> 1.15)
65
68
  rake (~> 13.2)
66
69
  rspec (~> 3.13)
67
70
  rubocop (~> 1.70)
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # AdfBuilder
2
- Hopefully this will help with development in the ADF format. The goal is to intuitively create and update an ADF XML file that can easily be added to an email or saved to a file.
2
+
3
+ A Ruby gem for creating valid **Auto-lead Data Format (ADF)** XML documents intuitively.
4
+
5
+ Version **1.0** introduces a new declarative DSL, strict validation, and a robust architecture.
3
6
 
4
7
  ## Installation
5
8
 
@@ -14,393 +17,134 @@ And then execute:
14
17
  $ bundle install
15
18
  ```
16
19
 
17
- Or install it yourself as:
18
- ```shell
19
- $ gem install adf_builder
20
- ```
21
-
22
20
  ## Usage
23
21
 
24
- Quickly create the minimal lead found in the spec document
25
- ```ruby
26
- builder = AdfBuilder::Builder.new
27
- builder.minimal_lead
28
- ```
29
- Start Building
30
- ```ruby
31
- builder = AdfBuilder::Builder.new
32
- builder.to_xml
33
- ```
22
+ ### The DSL (New in v1.0)
34
23
 
24
+ Version 1.0 uses a block-based DSL to structure your ADF document. This aligns the visual structure of your Ruby code with the resulting XML hierarchy.
35
25
 
36
- Outputs
37
- ```xml
38
- <?ADF version="1.0"?>
39
-
40
- <?xml version="1.0"?>
41
- <adf>
42
- <prospect status="new">
43
- <requestdate>2021-08-04T15:18:30+04:00</requestdate>
44
- </prospect>
45
- </adf>
46
- ```
47
-
48
-
49
- Update Requestdate value
50
26
  ```ruby
51
- builder = AdfBuilder::Builder.new
52
- builder.prospect.request_date.update_val(Date.new(2021,12,12))
53
- ```
54
- Outputs
27
+ xml = AdfBuilder.build do
28
+ prospect do
29
+ # Singular attributes (last call wins)
30
+ request_date Time.now
31
+
32
+ # Multiple items: Calling 'vehicle' multiple times adds multiple vehicles
33
+ vehicle do
34
+ year 2021
35
+ make 'Ford'
36
+ model 'F-150'
37
+ status :new # Validated attribute
38
+ end
39
+
40
+ vehicle do
41
+ year 2023
42
+ make 'Tesla'
43
+ model 'Cybertruck'
44
+ status :used
45
+ end
46
+
47
+ customer do
48
+ contact do
49
+ name 'John Doe', part: 'full'
50
+ email 'john@example.com'
51
+ phone '555-1234', type: 'cell'
52
+ end
53
+ end
54
+
55
+ vendor do
56
+ # vendor implementation...
57
+ end
58
+ end
59
+ end
60
+
61
+ puts xml
62
+ ```
63
+
64
+ ### Outputs
55
65
 
56
66
  ```xml
57
- <?ADF version="1.0"?>
58
-
59
- <?xml version="1.0"?>
60
- <adf>
61
- <prospect status="new">
62
- <requestdate>2021-12-12T00:00:00+00:00</requestdate>
63
- </prospect>
64
- </adf>
65
- ```
66
-
67
-
68
- Add ID tag to Prospect
69
- ```ruby
70
- builder = AdfBuilder::Builder.new
71
- builder.prospect.add_id('howdy', 'Ag')
72
- ```
73
-
74
- Outputs
75
- ```xml
76
- <?ADF version="1.0"?>
77
-
78
67
  <?xml version="1.0"?>
79
- <adf>
80
- <prospect status="new">
81
- <id sequence="1" source="Ag">howdy</id>
82
- <requestdate>2021-08-04T15:24:16+04:00</requestdate>
83
- </prospect>
84
- </adf>
85
- ```
86
-
87
- Add Vehicle
88
-
89
- ```ruby
90
- builder = AdfBuilder::Builder.new
91
- builder.prospect.vehicles.add(2021, 'Ford', 'Raptor')
92
- ```
93
-
94
- Outputs
95
-
96
- ```xml
97
68
  <?ADF version="1.0"?>
98
-
99
- <?xml version="1.0"?>
100
69
  <adf>
101
- <prospect status="new">
102
- <requestdate>2021-08-04T18:08:50+04:00</requestdate>
103
- <vehicle interest="buy" status="new">
70
+ <prospect>
71
+ <requestdate>2026-01-19 16:40:00 -0600</requestdate>
72
+ <vehicle status="new">
104
73
  <year>2021</year>
105
74
  <make>Ford</make>
106
- <model>Raptor</model>
75
+ <model>F-150</model>
107
76
  </vehicle>
108
- </prospect>
109
- </adf>
110
- ```
111
-
112
- Vehicle with different operations
113
-
114
- ```ruby
115
- builder = AdfBuilder::Builder.new
116
- builder.prospect.vehicles.add(2021, 'Toyota', 'Prius', {
117
- status: :used,
118
- })
119
- builder.prospect.vehicles.update_tags_with_free_text(0, {
120
- bodystyle: 'howdy',
121
- year: '2000'
122
- })
123
- builder.prospect.vehicles.update_odometer(0, 9000, {
124
- units: 'km'
125
- })
126
- builder.prospect.vehicles.update_condition(0, 'ffff')
127
- builder.prospect.vehicles.update_imagetag(0, 'http://adfxml.info/adf_spec.pdf', {
128
- width: 400,
129
- height: 500,
130
- alttext: 'Howdy'
131
- })
132
- puts builder.to_xml
133
- ```
134
-
135
- Outputs
136
-
137
- ```xml
138
- <?ADF version="1.0"?>
139
-
140
- <?xml version="1.0"?>
141
- <adf>
142
- <prospect status="new">
143
- <requestdate>2021-08-09T00:53:59+04:00</requestdate>
144
- <customer/>
145
- <vendor/>
146
77
  <vehicle status="used">
147
- <year>2000</year>
148
- <make>Toyota</make>
149
- <model>Prius</model>
150
- <bodystyle>howdy</bodystyle>
151
- <odometer units="km">9000</odometer>
152
- <imagetag width="400" height="500" alttext="Howdy">http://adfxml.info/adf_spec.pdf</imagetag>
78
+ <year>2023</year>
79
+ <make>Tesla</make>
80
+ <model>Cybertruck</model>
153
81
  </vehicle>
154
- </prospect>
155
- </adf>
156
- ```
157
-
158
- Color Combination
159
-
160
- ```ruby
161
- builder = AdfBuilder::Builder.new
162
- builder.prospect.vehicles.add(2021, 'Toyota', 'Prius', {
163
- status: :used,
164
- })
165
- builder.prospect.vehicles.add_color_combination(0, 'black', 'yellow', 1)
166
- puts builder.to_xml
167
- ```
168
-
169
- Outputs
170
-
171
- ```xml
172
- <?ADF version="1.0"?>
173
-
174
- <?xml version="1.0"?>
175
- <adf>
176
- <prospect status="new">
177
- <requestdate>2021-08-09T00:56:07+04:00</requestdate>
178
- <customer/>
179
- <vendor/>
180
- <vehicle status="used">
181
- <year>2021</year>
182
- <make>Toyota</make>
183
- <model>Prius</model>
184
- <colorcombination>
185
- <interiorcolor>black</interiorcolor>
186
- <exteriorcolor>yellow</exteriorcolor>
187
- <preference>1</preference>
188
- </colorcombination>
189
- </vehicle>
190
- </prospect>
191
- </adf>
192
- ```
193
-
194
- Add Vendor
195
-
196
- ```ruby
197
- builder = AdfBuilder::Builder.new
198
- builder.prospect.vendor.add('Dealer One', 'Manager Name', {
199
- part: 'full',
200
- type: 'individual'
201
- }) # options for customer object that is required in vendor
202
- ```
203
-
204
- Outputs
205
-
206
- ```xml
207
- <?ADF version="1.0"?>
208
-
209
- <?xml version="1.0"?>
210
- <adf>
211
- <prospect status="new">
212
- <requestdate>2021-08-08T18:43:02+04:00</requestdate>
213
- <customer/>
214
- <vendor>
215
- <vendorname>Dealer One</vendorname>
216
- <contact>
217
- <name part="full" type="individual">Manager Name</name>
218
- </contact>
219
- </vendor>
220
- </prospect>
221
- </adf>
222
- ```
223
-
224
- Add Contact with phone
225
-
226
- ```ruby
227
- builder = AdfBuilder::Builder.new
228
- builder.prospect.customer.add('New Guy', {
229
- part: 'full',
230
- type: 'individual'
231
- })
232
- builder.prospect.customer.contact.add_phone('(555)-444-3333')
233
- ```
234
-
235
- Outputs
236
-
237
- ```xml
238
- <?ADF version="1.0"?>
239
-
240
- <?xml version="1.0"?>
241
- <adf>
242
- <prospect status="new">
243
- <requestdate>2021-08-08T18:44:45+04:00</requestdate>
244
82
  <customer>
245
83
  <contact>
246
- <name part="full" type="individual">New Guy</name>
247
- <phone>(555)-444-3333</phone>
84
+ <name part="full">John Doe</name>
85
+ <email>john@example.com</email>
86
+ <phone type="cell">555-1234</phone>
248
87
  </contact>
249
88
  </customer>
250
- <vendor/>
251
89
  </prospect>
252
90
  </adf>
253
91
  ```
254
- A complex Provider
255
92
 
256
- ```ruby
257
- builder = AdfBuilder::Builder.new
258
- provider = builder.prospect.provider
259
- provider.add('Testing', {part: 'full', type: 'business'})
260
- provider.update_tags_with_free_text({
261
- url: 'howdy',
262
- service: "Nice"
263
- })
264
- provider.add_email("test@test.com", {preferredcontact: 0})
265
- provider.add_phone("+14445556666", {
266
- type: 'fax',
267
- time: 'day'
268
- })
269
- provider.add_contact("Mr Sir")
270
- provider.contact.add_phone("+132435523424")
271
- ```
272
-
273
- ```xml
274
- <?ADF version="1.0"?>
93
+ ### Multiple vs Singular Items
275
94
 
276
- <?xml version="1.0"?>
277
- <adf>
278
- <prospect status="new">
279
- <requestdate>2021-08-12T18:56:41+04:00</requestdate>
280
- <customer/>
281
- <vendor/>
282
- <provider>
283
- <name part="full" type="business">Testing</name>
284
- <url>howdy</url>
285
- <service>Nice</service>
286
- <email preferredcontact="0">test@test.com</email>
287
- <phone type="fax" time="day">+14445556666</phone>
288
- <contact>
289
- <name>Mr Sir</name>
290
- <phone>+132435523424</phone>
291
- </contact>
292
- </provider>
293
- </prospect>
294
- </adf>
295
- ```
95
+ - **Singular Attributes**: Methods that take a value (e.g., `year 2021`, `request_date Time.now`) set an attribute on the current node. Calling them multiple times overwrites the value.
96
+ - **Multiple Nodes**: Methods that take a block (e.g., `vehicle { ... }`) create a new child node and append it. You can call them as many times as needed to add multiple items.
296
97
 
297
- Adding and Updating Price of Vehicle
298
- ```ruby
299
- builder = AdfBuilder::Builder.new
300
- builder.prospect.vehicles.add(2021, 'Toyota', 'Prius', {
301
- status: :used,
302
- })
98
+ ### Advanced: Editing & Programmatic Access
303
99
 
304
- builder.prospect.vehicles.add_price(0,23400, {
305
- type: 'quote',
306
- currency: 'blah',
307
- source: "YES"
308
- })
100
+ If you need to edit the data after building it (or build it progressively), use `AdfBuilder.tree`. This returns the object tree instead of the XML string.
309
101
 
310
- puts builder.to_xml
102
+ ```ruby
103
+ # 1. Build the tree
104
+ tree = AdfBuilder.tree do
105
+ prospect do
106
+ vehicle do
107
+ year 2020
108
+ make 'Ford'
109
+ status :new
110
+ end
111
+ end
112
+ end
311
113
 
312
- builder.prospect.vehicles.price(0).update(3444, {
313
- currency: 'USD'
314
- })
114
+ # 2. Modify the tree programmatically
115
+ # Access helpers: .prospect, .vehicles, .customers
116
+ prospect = tree.children.first
117
+ vehicle = prospect.vehicles.first
315
118
 
316
- puts builder.to_xml
317
- ```
119
+ vehicle.year(2025) # Update existing value
120
+ vehicle.status(:used)
318
121
 
319
- Outputs
320
- ```xml
321
- <?ADF version="1.0"?>
122
+ # Add a new vehicle dynamically
123
+ prospect.vehicle do
124
+ year 2023
125
+ make 'Tesla'
126
+ status :new
127
+ end
322
128
 
323
- <?xml version="1.0"?>
324
- <adf>
325
- <prospect status="new">
326
- <requestdate>2021-08-13T13:28:50+04:00</requestdate>
327
- <customer/>
328
- <vendor/>
329
- <vehicle status="used">
330
- <year>2021</year>
331
- <make>Toyota</make>
332
- <model>Prius</model>
333
- <price type="quote" source="YES">23400</price>
334
- </vehicle>
335
- </prospect>
336
- </adf>
129
+ # 3. Serialize to XML
130
+ puts tree.to_xml
337
131
  ```
338
132
 
339
- ```xml
340
- <?ADF version="1.0"?>
341
-
342
- <?xml version="1.0"?>
133
+ ### Validation
343
134
 
344
- <adf>
345
- <prospect status="new">
346
- <requestdate>2021-08-13T13:28:50+04:00</requestdate>
347
- <customer/>
348
- <vendor/>
349
- <vehicle status="used">
350
- <year>2021</year>
351
- <make>Toyota</make>
352
- <model>Prius</model>
353
- <price type="quote" source="YES" currency="USD">3444</price>
354
- </vehicle>
355
- </prospect>
356
- </adf>
357
- ```
135
+ The library now enforces ADF standards. Invalid enum values will raise an error.
358
136
 
359
- Adding comments to Vehicle
360
137
  ```ruby
361
- builder = AdfBuilder::Builder.new
362
- builder.prospect.vehicles.add(2021, 'Toyota', 'Prius', {
363
- status: :used,
364
- })
365
-
366
- builder.prospect.vehicles.add_comments(0, "This is a comment")
367
-
368
- puts builder.to_xml
369
- ```
370
-
371
- Outputs
372
- ```xml
373
- <?ADF version="1.0"?>
374
-
375
- <?xml version="1.0"?>
376
- <adf>
377
- <prospect status="new">
378
- <requestdate>2021-11-02T16:35:43+04:00</requestdate>
379
- <customer/>
380
- <vendor/>
381
- <vehicle status="used">
382
- <year>2021</year>
383
- <make>Toyota</make>
384
- <model>Prius</model>
385
- <comments>This is a comment</comments>
386
- </vehicle>
387
- </prospect>
388
- </adf>
138
+ # Raises AdfBuilder::Error: Invalid value for status: broken
139
+ AdfBuilder.build do
140
+ prospect do
141
+ vehicle do
142
+ status :broken
143
+ end
144
+ end
145
+ end
389
146
  ```
390
- ## Development
391
-
392
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
393
-
394
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
395
147
 
396
148
  ## Contributing
397
149
 
398
- Bug reports and pull requests are welcome on GitHub at https://github.com/jippylong12/adf_builder. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jippylong12/adf_builder/blob/master/CODE_OF_CONDUCT.md).
399
-
400
- ## License
401
-
402
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
403
-
404
- ## Code of Conduct
405
-
406
- Everyone interacting in the AdfBuilder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jippylong12/adf_builder/blob/master/CODE_OF_CONDUCT.md).
150
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jippylong12/adf_builder.
data/adf_builder.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  # Uncomment to register a new dependency of your gem
31
31
  spec.add_dependency "ox", "~> 2.14"
32
+ spec.add_development_dependency "nokogiri", "~> 1.15"
32
33
  spec.add_development_dependency "rake", "~> 13.2"
33
34
  spec.add_development_dependency "rspec", "~> 3.13"
34
35
  spec.add_development_dependency "rubocop", "~> 1.70"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdfBuilder
4
+ class DSL
5
+ def self.build(&block)
6
+ root = Nodes::Root.new
7
+ root.instance_eval(&block) if block_given?
8
+ root.validate!
9
+ Serializer.to_xml(root)
10
+ end
11
+
12
+ def self.tree(&block)
13
+ root = Nodes::Root.new
14
+ root.instance_eval(&block) if block_given?
15
+ root
16
+ end
17
+ end
18
+
19
+ def self.build(&block)
20
+ DSL.build(&block)
21
+ end
22
+
23
+ def self.tree(&block)
24
+ DSL.tree(&block)
25
+ end
26
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdfBuilder
4
+ module Nodes
5
+ class Customer < Node
6
+ def initialize
7
+ super
8
+ @tag_name = :customer
9
+ end
10
+
11
+ def contact(&block)
12
+ remove_children(:contact)
13
+ contact = Contact.new
14
+ contact.instance_eval(&block) if block_given?
15
+ add_child(contact)
16
+ end
17
+
18
+ def id(value, sequence: nil, source: nil)
19
+ # id* is multiple
20
+ add_child(Id.new(value, sequence: sequence, source: source))
21
+ end
22
+
23
+ def timeframe(&block)
24
+ remove_children(:timeframe)
25
+ tf = Timeframe.new
26
+ tf.instance_eval(&block) if block_given?
27
+ add_child(tf)
28
+ end
29
+
30
+ def comments(value)
31
+ remove_children(:comments)
32
+ add_child(GenericNode.new(:comments, {}, value))
33
+ end
34
+ end
35
+
36
+ class Timeframe < Node
37
+ def initialize
38
+ super
39
+ @tag_name = :timeframe
40
+ end
41
+
42
+ def description(value)
43
+ remove_children(:description)
44
+ add_child(GenericNode.new(:description, {}, value))
45
+ end
46
+
47
+ def earliestdate(value)
48
+ remove_children(:earliestdate)
49
+ add_child(GenericNode.new(:earliestdate, {}, value))
50
+ end
51
+
52
+ def latestdate(value)
53
+ remove_children(:latestdate)
54
+ add_child(GenericNode.new(:latestdate, {}, value))
55
+ end
56
+ end
57
+ end
58
+ end