shale 0.6.0 → 0.7.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: 243b99a073ac189f66ae0fbfedd94a5715399de731d0e0fd1d970dad749d27a0
4
- data.tar.gz: d2f57de2427574d710d6a6fe9ba35868fd162214ce8aa2092ddfb3fe1f5e7c3d
3
+ metadata.gz: 5f3c860cf358f78476f8f22a86006694c3d9fd6cb554c945d7364bbf8bb70815
4
+ data.tar.gz: 5eba158f49fdcfb3012f2e4dcf804fdc4c8fe1e0c37e8e3dbb37972aa469c8bb
5
5
  SHA512:
6
- metadata.gz: 9de1be5eccbb647f05b6573d783491f858fb56614f09c7c625e7fa922c7b89b5d38863a57defbe506d42f0b542b326e4ca61ecf308b94f7b35b4056751e0299e
7
- data.tar.gz: a1e29fc097c130c55e45837af198e3cd4f7d3ad6a94b17d032eb31b8065f7ea013682836256d12127480965ed3733079e5badd7a8b0060e3bc60f00734fcc215
6
+ metadata.gz: b002a6837137fa024788e50b2b408ebd7d80f1fd73c7bf1182a2d0b0ab784a07cbda08147af703c4ded76efcb086958049c3d82573e5f885d1b1745f9312eb0c
7
+ data.tar.gz: e66ba2657a540ad6e9cebd98cf4a61a9ae4b31c0fc6ba1e8085493cc727eda48e2cb87515d399a2524cac8bb4d92932f57dd548afa2ba1d8fb5b97bb5c8ef1a7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.7.0] - [2022-08-09]
2
+
3
+ ### Added
4
+ - `only: []` and `except: []` options that allow to controll what attributes are rendered/parsed
5
+ - `render_nil: true` option that allows to render nil values
6
+ - Allow to pass a context object to extractor/generator methods
7
+
8
+ ### Changed
9
+ - Pass whole document to methods for JSON/YAML/TOML so its behavior is consistent with XML mapping
10
+ - Convert splat arguments to keyword arguments
11
+ - RSpec: enable random spec execution and warnings
12
+
1
13
  ## [0.6.0] - 2022-07-05
2
14
 
3
15
  ### Added
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 TODO: Write your name
3
+ Copyright (c) 2021 Kamil Giszczak
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -12,7 +12,7 @@ Documentation with interactive examples is available at [Shale website](https://
12
12
  * Convert Ruby data model to JSON, YAML, TOML and XML
13
13
  * Generate JSON and XML Schema from Ruby models
14
14
  * Compile JSON and XML Schema into Ruby models
15
- * Out of the box support for JSON, YAML, toml-rb, Nokogiri, REXML and Ox parsers
15
+ * Out of the box support for JSON, YAML, Tomlib, toml-rb, Nokogiri, REXML and Ox parsers
16
16
  * Support for custom adapters
17
17
 
18
18
  ## Installation
@@ -57,8 +57,9 @@ $ gem install shale
57
57
  * [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)
58
58
  * [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)
59
59
  * [Using XML namespaces](#using-xml-namespaces)
60
+ * [Rendering nil values](#rendering-nil-values)
60
61
  * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
61
- * [Pretty printing and XML declaration](#pretty-printing-and-xml-declaration)
62
+ * [Additional options](#additional-options)
62
63
  * [Using custom models](#using-custom-models)
63
64
  * [Supported types](#supported-types)
64
65
  * [Writing your own type](#writing-your-own-type)
@@ -202,18 +203,23 @@ person.to_yaml
202
203
  ### Converting TOML to object
203
204
 
204
205
  To use TOML with Shale you have to set adapter you want to use.
205
- Shale comes with adapter for [toml-rb](https://github.com/emancu/toml-rb).
206
+ Out of the box Shale suports [Tomlib](https://github.com/kgiszczak/tomlib).
207
+ It also comes with adapter for [toml-rb](https://github.com/emancu/toml-rb) if you prefer that.
206
208
  For details see [Adapters](#adapters) section.
207
209
 
208
- To set it, first make sure toml-rb gem is installed:
210
+ To set it, first make sure Tomlib gem is installed:
209
211
 
210
212
  ```
211
- $ gem install shale
213
+ $ gem install tomlib
212
214
  ```
213
215
 
214
216
  then setup adapter:
215
217
 
216
218
  ```ruby
219
+ require 'tomlib'
220
+ Shale.toml_adapter = Tomlib
221
+
222
+ # Alternatively if you'd like to use toml-rb, use:
217
223
  require 'shale/adapter/toml_rb'
218
224
  Shale.toml_adapter = Shale::Adapter::TomlRB
219
225
  ```
@@ -241,18 +247,16 @@ person.to_toml
241
247
 
242
248
  # =>
243
249
  #
244
- # ---
245
- # first_name: John
246
- # last_name: Doe
247
- # age: 50
248
- # married: false
249
- # hobbies:
250
- # - Singing
251
- # - Dancing
252
- # address:
253
- # city: London
254
- # street: Oxford Street
255
- # zip: E1 6AN
250
+ # first_name = "John"
251
+ # last_name = "Doe"
252
+ # age = 50
253
+ # married = false
254
+ # hobbies = [ "Singing", "Dancing" ]
255
+ #
256
+ # [address]
257
+ # city = "London"
258
+ # street = "Oxford Street"
259
+ # zip = "E1 6AN"
256
260
  ```
257
261
 
258
262
  ### Converting Hash to object
@@ -555,6 +559,52 @@ person = Person.from_xml(<<~DATA)
555
559
  DATA
556
560
  ```
557
561
 
562
+ ### Rendering nil values
563
+
564
+ By default elements with `nil` value are not rendered. You can change this behavior
565
+ by using `render_nil: true` on a mapping.
566
+
567
+ ```ruby
568
+ class Person < Shale::Mapper
569
+ attribute :first_name, Shale::Type::String
570
+ attribute :last_name, Shale::Type::String
571
+ attribute :age, Shale::Type::Integer
572
+
573
+ json do
574
+ map 'first_name', to: :first_name, render_nil: true
575
+ map 'last_name', to: :last_name, render_nil: false
576
+ map 'age', to: :age, render_nil: true
577
+ end
578
+
579
+ xml do
580
+ root 'person'
581
+
582
+ map_element 'first_name', to: :first_name, render_nil: true
583
+ map_element 'last_name', to: :last_name, render_nil: false
584
+ map_attribute 'age', to: :age, render_nil: true
585
+ end
586
+ end
587
+
588
+ person = Person.new(first_name: nil, last_name: nil, age: nil)
589
+
590
+ puts person.to_json(pretty: true)
591
+
592
+ # =>
593
+ #
594
+ # {
595
+ # "first_name": null,
596
+ # "age": "null"
597
+ # }
598
+
599
+ puts person.to_xml(pretty: true)
600
+
601
+ # =>
602
+ #
603
+ # <person age="">
604
+ # <first_name/>
605
+ # </person>
606
+ ```
607
+
558
608
  ### Using methods to extract and generate data
559
609
 
560
610
  If you need full controll over extracting and generating data from/to document,
@@ -582,8 +632,8 @@ class Person < Shale::Mapper
582
632
  model.hobbies = value.split(',').map(&:strip)
583
633
  end
584
634
 
585
- def hobbies_to_json(model)
586
- model.hobbies.join(', ')
635
+ def hobbies_to_json(model, doc)
636
+ doc['hobbies'] = model.hobbies.join(', ')
587
637
  end
588
638
 
589
639
  def address_from_json(model, value)
@@ -591,8 +641,8 @@ class Person < Shale::Mapper
591
641
  model.city = value['city']
592
642
  end
593
643
 
594
- def address_to_json(model)
595
- { 'street' => model.street, 'city' => model.city }
644
+ def address_to_json(model, doc)
645
+ doc['address'] = { 'street' => model.street, 'city' => model.city }
596
646
  end
597
647
 
598
648
  def hobbies_from_xml(model, value)
@@ -649,12 +699,100 @@ DATA
649
699
  # @city="London">
650
700
  ```
651
701
 
652
- ### Pretty printing and XML declaration
702
+ You can also pass a `context` object that will be available in extractor/generator methods:
703
+
704
+ ```ruby
705
+ class Person < Shale::Mapper
706
+ attribute :password, Shale::Type::String
653
707
 
654
- If you need formatted output you can pass `:pretty` parameter to `#to_json` and `#to_xml`
708
+ json do
709
+ map 'password', using: { from: :password_from_json, to: :password_to_json }
710
+ end
711
+
712
+ def password_from_json(model, value, context)
713
+ if context.admin?
714
+ model.password = value
715
+ else
716
+ model.password = '*****'
717
+ end
718
+ end
719
+
720
+ def password_to_json(model, doc, context)
721
+ if context.admin?
722
+ doc['password'] = model.password
723
+ else
724
+ doc['password'] = '*****'
725
+ end
726
+ end
727
+ end
728
+
729
+ Person.new(password: 'secret').to_json(context: current_user)
730
+ ```
731
+
732
+ ### Additional options
733
+
734
+ You can control which attributes to render and parse by
735
+ using `only: []` and `except: []` parameters.
655
736
 
656
737
  ```ruby
657
- person.to_json(:pretty)
738
+ # e.g. if you have this model graph:
739
+ person = Person.new(
740
+ first_name: 'John'
741
+ last_name: 'Doe',
742
+ address: Address.new(city: 'London', street: 'Oxford Street')
743
+ )
744
+
745
+ # if you want to render only `first_name` and `address.city` do:
746
+ person.to_json(only: [:first_name, address: [:city]], pretty: true)
747
+
748
+ # =>
749
+ #
750
+ # {
751
+ # "first_name": "John",
752
+ # "address": {
753
+ # "city": "London"
754
+ # }
755
+ # }
756
+
757
+ # and if you don't need an address you can do:
758
+ person.to_json(except: [:address], pretty: true)
759
+
760
+ # =>
761
+ #
762
+ # {
763
+ # "first_name": "John",
764
+ # "last_name": "Doe"
765
+ # }
766
+ ```
767
+
768
+ It works the same for parsing:
769
+
770
+ ```ruby
771
+ # e.g. if you want to parse only `address.city` do:
772
+ Person.from_json(doc, only: [address: [:city]])
773
+
774
+ # =>
775
+ #
776
+ # #<Person:0x0000000113d7a488
777
+ # @first_name=nil,
778
+ # @last_name=nil,
779
+ # @address=#<Address:0x0000000113d7a140 @street=nil, @city="London">>
780
+
781
+ # and if you don't need an `address`:
782
+ Person.from_json(doc, except: [:address])
783
+
784
+ # =>
785
+ #
786
+ # #<Person:0x0000000113d7a488
787
+ # @first_name="John",
788
+ # @last_name="Doe",
789
+ # @address=nil>
790
+ ```
791
+
792
+ If you need formatted output you can pass `pretty: true` parameter to `#to_json` and `#to_xml`
793
+
794
+ ```ruby
795
+ person.to_json(pretty: true)
658
796
 
659
797
  # =>
660
798
  #
@@ -666,10 +804,10 @@ person.to_json(:pretty)
666
804
  # }
667
805
  ```
668
806
 
669
- You can also add an XML declaration by passing `:declaration` to `#to_xml`
807
+ You can also add an XML declaration by passing `declaration: true` to `#to_xml`
670
808
 
671
809
  ```ruby
672
- person.to_xml(:pretty, :declaration)
810
+ person.to_xml(pretty: true, declaration: true)
673
811
 
674
812
  # =>
675
813
  #
@@ -726,7 +864,7 @@ DATA
726
864
  # @last_name="Doe",
727
865
  # @address=#<Address:0x0000000113d7a140 @street="Oxford Street", @city="London">>
728
866
 
729
- PersonMapper.to_json(person, :pretty)
867
+ PersonMapper.to_json(person, pretty: true)
730
868
 
731
869
  # =>
732
870
  #
@@ -781,12 +919,17 @@ Shale.json_adapter = MultiJson
781
919
  Shale.yaml_adapter = MyYamlAdapter
782
920
  ```
783
921
 
784
- To handle TOML documents you have to set TOML adapter.
785
- Shale provides adapter for `toml-rb` TOML parser:
922
+ To handle TOML documents you have to set TOML adapter. Out of the box `Tomlib` is supported.
923
+ Shale also provides adapter for `toml-rb` parser:
786
924
 
787
925
  ```ruby
788
926
  require 'shale'
789
927
 
928
+ # if you want to use Tomlib
929
+ require 'tomlib'
930
+ Shale.toml_adapter = Tomlib
931
+
932
+ # if you want to use toml-rb
790
933
  require 'shale/adapter/toml_rb'
791
934
  Shale.toml_adapter = Shale::Adapter::TomlRB
792
935
  ```
@@ -22,13 +22,13 @@ module Shale
22
22
  # Serialize Hash into JSON
23
23
  #
24
24
  # @param [Hash] obj Hash object
25
- # @param [Array<Symbol>] options
25
+ # @param [true, false] pretty
26
26
  #
27
27
  # @return [String]
28
28
  #
29
29
  # @api private
30
- def self.dump(obj, *options)
31
- if options.include?(:pretty)
30
+ def self.dump(obj, pretty: false)
31
+ if pretty
32
32
  ::JSON.pretty_generate(obj)
33
33
  else
34
34
  ::JSON.generate(obj)
@@ -36,25 +36,26 @@ module Shale
36
36
  # Serialize Nokogiri document into XML
37
37
  #
38
38
  # @param [::Nokogiri::XML::Document] doc Nokogiri document
39
- # @param [Array<Symbol>] options
39
+ # @param [true, false] pretty
40
+ # @param [true, false] declaration
40
41
  #
41
42
  # @return [String]
42
43
  #
43
44
  # @api private
44
- def self.dump(doc, *options)
45
+ def self.dump(doc, pretty: false, declaration: false)
45
46
  save_with = ::Nokogiri::XML::Node::SaveOptions::AS_XML
46
47
 
47
- if options.include?(:pretty)
48
+ if pretty
48
49
  save_with |= ::Nokogiri::XML::Node::SaveOptions::FORMAT
49
50
  end
50
51
 
51
- unless options.include?(:declaration)
52
+ unless declaration
52
53
  save_with |= ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
53
54
  end
54
55
 
55
56
  result = doc.to_xml(save_with: save_with)
56
57
 
57
- unless options.include?(:pretty)
58
+ unless pretty
58
59
  result = result.sub(/\n/, '')
59
60
  end
60
61
 
@@ -30,19 +30,20 @@ module Shale
30
30
  # Serialize Ox document into XML
31
31
  #
32
32
  # @param [::Ox::Document, ::Ox::Element] doc Ox document
33
- # @param [Array<Symbol>] options
33
+ # @param [true, false] pretty
34
+ # @param [true, false] declaration
34
35
  #
35
36
  # @return [String]
36
37
  #
37
38
  # @api private
38
- def self.dump(doc, *options)
39
+ def self.dump(doc, pretty: false, declaration: false)
39
40
  opts = { indent: -1, with_xml: false }
40
41
 
41
- if options.include?(:pretty)
42
+ if pretty
42
43
  opts[:indent] = 2
43
44
  end
44
45
 
45
- if options.include?(:declaration)
46
+ if declaration
46
47
  doc[:version] = '1.0'
47
48
  opts[:with_xml] = true
48
49
  end
@@ -70,7 +70,7 @@ module Shale
70
70
  #
71
71
  # @api private
72
72
  def add_attribute(element, name, value)
73
- element.add_attribute(name, value)
73
+ element.add_attribute(name, value || '')
74
74
  end
75
75
 
76
76
  # Add child element to REXML element
@@ -31,19 +31,20 @@ module Shale
31
31
  # Serialize REXML document into XML
32
32
  #
33
33
  # @param [::REXML::Document] doc REXML document
34
- # @param [Array<Symbol>] options
34
+ # @param [true, false] pretty
35
+ # @param [true, false] declaration
35
36
  #
36
37
  # @return [String]
37
38
  #
38
39
  # @api private
39
- def self.dump(doc, *options)
40
- if options.include?(:declaration)
40
+ def self.dump(doc, pretty: false, declaration: false)
41
+ if declaration
41
42
  doc.add(::REXML::XMLDecl.new)
42
43
  end
43
44
 
44
45
  io = StringIO.new
45
46
 
46
- if options.include?(:pretty)
47
+ if pretty
47
48
  formatter = ::REXML::Formatters::Pretty.new
48
49
  formatter.compact = true
49
50
  else
data/lib/shale/error.rb CHANGED
@@ -7,6 +7,11 @@ module Shale
7
7
  TOML Adapter is not set.
8
8
  To use Shale with TOML documents you have to install parser and set adapter.
9
9
 
10
+ # To use Tomlib:
11
+ # Make sure tomlib is installed eg. execute: gem install tomlib
12
+ Shale.toml_adapter = Tomlib
13
+
14
+ # To use toml-rb:
10
15
  # Make sure toml-rb is installed eg. execute: gem install toml-rb
11
16
  require 'shale/adapter/toml_rb'
12
17
  Shale.toml_adapter = Shale::Adapter::TomlRB
@@ -18,16 +23,16 @@ module Shale
18
23
  XML Adapter is not set.
19
24
  To use Shale with XML documents you have to install parser and set adapter.
20
25
 
21
- To use REXML:
26
+ # To use REXML:
22
27
  require 'shale/adapter/rexml'
23
28
  Shale.xml_adapter = Shale::Adapter::REXML
24
29
 
25
- To use Nokogiri:
30
+ # To use Nokogiri:
26
31
  # Make sure Nokogiri is installed eg. execute: gem install nokogiri
27
32
  require 'shale/adapter/nokogiri'
28
33
  Shale.xml_adapter = Shale::Adapter::Nokogiri
29
34
 
30
- To use OX:
35
+ # To use OX:
31
36
  # Make sure Ox is installed eg. execute: gem install ox
32
37
  require 'shale/adapter/ox'
33
38
  Shale.xml_adapter = Shale::Adapter::Ox
@@ -40,17 +40,28 @@ module Shale
40
40
  # @param [String] name
41
41
  # @param [Symbol, nil] attribute
42
42
  # @param [Hash, nil] methods
43
+ # @param [true, false] render_nil
43
44
  #
44
45
  # @api private
45
- def initialize(name:, attribute:, methods:)
46
+ def initialize(name:, attribute:, methods:, render_nil:)
46
47
  @name = name
47
48
  @attribute = attribute
49
+ @render_nil = render_nil
48
50
 
49
51
  if methods
50
52
  @method_from = methods[:from]
51
53
  @method_to = methods[:to]
52
54
  end
53
55
  end
56
+
57
+ # Check render_nil
58
+ #
59
+ # @return [true, false]
60
+ #
61
+ # @api private
62
+ def render_nil?
63
+ @render_nil == true
64
+ end
54
65
  end
55
66
  end
56
67
  end
@@ -30,10 +30,11 @@ module Shale
30
30
  # @param [Hash, nil] methods
31
31
  # @param [Shale::Mapping::XmlNamespace] namespace
32
32
  # @param [true, false] cdata
33
+ # @param [true, false] render_nil
33
34
  #
34
35
  # @api private
35
- def initialize(name:, attribute:, methods:, namespace:, cdata:)
36
- super(name: name, attribute: attribute, methods: methods)
36
+ def initialize(name:, attribute:, methods:, namespace:, cdata:, render_nil:)
37
+ super(name: name, attribute: attribute, methods: methods, render_nil: render_nil)
37
38
  @namespace = namespace
38
39
  @cdata = cdata
39
40
  end
@@ -30,13 +30,19 @@ module Shale
30
30
  # @param [String] key Document's key
31
31
  # @param [Symbol, nil] to Object's attribute
32
32
  # @param [Hash, nil] using
33
+ # @param [true, false] render_nil
33
34
  #
34
35
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
35
36
  #
36
37
  # @api private
37
- def map(key, to: nil, using: nil)
38
+ def map(key, to: nil, using: nil, render_nil: false)
38
39
  Validator.validate_arguments(key, to, using)
39
- @keys[key] = Descriptor::Dict.new(name: key, attribute: to, methods: using)
40
+ @keys[key] = Descriptor::Dict.new(
41
+ name: key,
42
+ attribute: to,
43
+ methods: using,
44
+ render_nil: render_nil
45
+ )
40
46
  end
41
47
 
42
48
  # Set the "finalized" instance variable to true
@@ -83,7 +83,8 @@ module Shale
83
83
  using: nil,
84
84
  namespace: :undefined,
85
85
  prefix: :undefined,
86
- cdata: false
86
+ cdata: false,
87
+ render_nil: false
87
88
  )
88
89
  Validator.validate_arguments(element, to, using)
89
90
  Validator.validate_namespace(element, namespace, prefix)
@@ -103,7 +104,8 @@ module Shale
103
104
  attribute: to,
104
105
  methods: using,
105
106
  namespace: Descriptor::XmlNamespace.new(nsp, pfx),
106
- cdata: cdata
107
+ cdata: cdata,
108
+ render_nil: render_nil
107
109
  )
108
110
  end
109
111
 
@@ -118,7 +120,14 @@ module Shale
118
120
  # @raise [IncorrectMappingArgumentsError] when arguments are incorrect
119
121
  #
120
122
  # @api private
121
- def map_attribute(attribute, to: nil, using: nil, namespace: nil, prefix: nil)
123
+ def map_attribute(
124
+ attribute,
125
+ to: nil,
126
+ using: nil,
127
+ namespace: nil,
128
+ prefix: nil,
129
+ render_nil: false
130
+ )
122
131
  Validator.validate_arguments(attribute, to, using)
123
132
  Validator.validate_namespace(attribute, namespace, prefix)
124
133
 
@@ -129,7 +138,8 @@ module Shale
129
138
  attribute: to,
130
139
  methods: using,
131
140
  namespace: Descriptor::XmlNamespace.new(namespace, prefix),
132
- cdata: false
141
+ cdata: false,
142
+ render_nil: render_nil
133
143
  )
134
144
  end
135
145
 
@@ -146,7 +156,8 @@ module Shale
146
156
  attribute: to,
147
157
  methods: using,
148
158
  namespace: nil,
149
- cdata: cdata
159
+ cdata: cdata,
160
+ render_nil: false
150
161
  )
151
162
  end
152
163
 
@@ -124,9 +124,7 @@ module Shale
124
124
  # @api public
125
125
  def to_schema(klass, id: nil, title: nil, description: nil, pretty: false)
126
126
  schema = as_schema(klass, id: id, title: title, description: description)
127
- options = pretty ? :pretty : nil
128
-
129
- Shale.json_adapter.dump(schema, options)
127
+ Shale.json_adapter.dump(schema, pretty: pretty)
130
128
  end
131
129
 
132
130
  private
@@ -35,8 +35,8 @@ module Shale
35
35
  def as_xml(doc)
36
36
  import = doc.create_element('xs:import')
37
37
 
38
- doc.add_attribute(import, 'namespace', @namespace)
39
- doc.add_attribute(import, 'schemaLocation', @location)
38
+ doc.add_attribute(import, 'namespace', @namespace) if @namespace
39
+ doc.add_attribute(import, 'schemaLocation', @location) if @location
40
40
 
41
41
  import
42
42
  end
@@ -222,13 +222,11 @@ module Shale
222
222
  def to_schemas(klass, base_name = nil, pretty: false, declaration: false)
223
223
  schemas = as_schemas(klass, base_name)
224
224
 
225
- options = [
226
- pretty ? :pretty : nil,
227
- declaration ? :declaration : nil,
228
- ]
229
-
230
225
  schemas.to_h do |schema|
231
- [schema.name, Shale.xml_adapter.dump(schema.as_xml, *options)]
226
+ [
227
+ schema.name,
228
+ Shale.xml_adapter.dump(schema.as_xml, pretty: pretty, declaration: declaration),
229
+ ]
232
230
  end
233
231
  end
234
232