representable 2.4.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -12
- data/CHANGES.md +6 -27
- data/README.md +28 -1326
- data/lib/representable.rb +4 -14
- data/lib/representable/binding.rb +3 -7
- data/lib/representable/definition.rb +1 -2
- data/lib/representable/populator.rb +35 -0
- data/lib/representable/version.rb +1 -1
- data/test/definition_test.rb +0 -7
- data/test/exec_context_test.rb +2 -2
- data/test/instance_test.rb +0 -19
- data/test/realistic_benchmark.rb +0 -13
- data/test/representable_test.rb +0 -16
- data/test/skip_test.rb +1 -1
- data/test/test_helper.rb +0 -2
- metadata +3 -65
- data/lib/representable/deprecations.rb +0 -127
- data/lib/representable/parse_strategies.rb +0 -93
- data/test-with-deprecations/as_test.rb +0 -65
- data/test-with-deprecations/benchmarking.rb +0 -83
- data/test-with-deprecations/binding_test.rb +0 -46
- data/test-with-deprecations/blaaaaaaaa_test.rb +0 -69
- data/test-with-deprecations/cached_test.rb +0 -147
- data/test-with-deprecations/class_test.rb +0 -119
- data/test-with-deprecations/coercion_test.rb +0 -52
- data/test-with-deprecations/config/inherit_test.rb +0 -135
- data/test-with-deprecations/config_test.rb +0 -122
- data/test-with-deprecations/decorator_scope_test.rb +0 -28
- data/test-with-deprecations/decorator_test.rb +0 -96
- data/test-with-deprecations/default_test.rb +0 -34
- data/test-with-deprecations/defaults_options_test.rb +0 -93
- data/test-with-deprecations/definition_test.rb +0 -264
- data/test-with-deprecations/example.rb +0 -310
- data/test-with-deprecations/examples/object.rb +0 -31
- data/test-with-deprecations/exec_context_test.rb +0 -93
- data/test-with-deprecations/features_test.rb +0 -70
- data/test-with-deprecations/filter_test.rb +0 -57
- data/test-with-deprecations/for_collection_test.rb +0 -74
- data/test-with-deprecations/generic_test.rb +0 -116
- data/test-with-deprecations/getter_setter_test.rb +0 -21
- data/test-with-deprecations/hash_bindings_test.rb +0 -87
- data/test-with-deprecations/hash_test.rb +0 -160
- data/test-with-deprecations/heritage_test.rb +0 -62
- data/test-with-deprecations/if_test.rb +0 -79
- data/test-with-deprecations/include_exclude_test.rb +0 -88
- data/test-with-deprecations/inherit_test.rb +0 -159
- data/test-with-deprecations/inline_test.rb +0 -272
- data/test-with-deprecations/instance_test.rb +0 -266
- data/test-with-deprecations/is_representable_test.rb +0 -77
- data/test-with-deprecations/json_test.rb +0 -355
- data/test-with-deprecations/lonely_test.rb +0 -239
- data/test-with-deprecations/mongoid_test.rb +0 -31
- data/test-with-deprecations/nested_test.rb +0 -115
- data/test-with-deprecations/object_test.rb +0 -60
- data/test-with-deprecations/parse_pipeline_test.rb +0 -64
- data/test-with-deprecations/parse_strategy_test.rb +0 -279
- data/test-with-deprecations/pass_options_test.rb +0 -27
- data/test-with-deprecations/pipeline_test.rb +0 -277
- data/test-with-deprecations/populator_test.rb +0 -105
- data/test-with-deprecations/prepare_test.rb +0 -67
- data/test-with-deprecations/private_options_test.rb +0 -18
- data/test-with-deprecations/reader_writer_test.rb +0 -19
- data/test-with-deprecations/realistic_benchmark.rb +0 -115
- data/test-with-deprecations/render_nil_test.rb +0 -21
- data/test-with-deprecations/represent_test.rb +0 -88
- data/test-with-deprecations/representable_test.rb +0 -511
- data/test-with-deprecations/schema_test.rb +0 -148
- data/test-with-deprecations/serialize_deserialize_test.rb +0 -33
- data/test-with-deprecations/skip_test.rb +0 -81
- data/test-with-deprecations/stringify_hash_test.rb +0 -41
- data/test-with-deprecations/test_helper.rb +0 -135
- data/test-with-deprecations/test_helper_test.rb +0 -25
- data/test-with-deprecations/uncategorized_test.rb +0 -67
- data/test-with-deprecations/user_options_test.rb +0 -15
- data/test-with-deprecations/wrap_test.rb +0 -152
- data/test-with-deprecations/xml_bindings_test.rb +0 -62
- data/test-with-deprecations/xml_test.rb +0 -503
- data/test-with-deprecations/yaml_test.rb +0 -162
- data/test/parse_strategy_test.rb +0 -279
@@ -1,119 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class ClassTest < BaseTest
|
4
|
-
|
5
|
-
class RepresentingSong
|
6
|
-
attr_reader :name
|
7
|
-
|
8
|
-
def from_hash(doc, *args)
|
9
|
-
@name = doc["__name__"]
|
10
|
-
|
11
|
-
self # DISCUSS: do we wanna be able to return whatever we want here? this is a trick to replace the actual object
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
describe "class: ClassName, only" do
|
17
|
-
representer! do
|
18
|
-
property :song, :class => RepresentingSong # supposed this class exposes #from_hash itself.
|
19
|
-
end
|
20
|
-
|
21
|
-
it "creates fresh instance and doesn't extend" do
|
22
|
-
song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song
|
23
|
-
song.must_be_instance_of RepresentingSong
|
24
|
-
song.name.must_equal "Captured"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
describe "class: lambda, only" do
|
30
|
-
representer! do
|
31
|
-
property :song, :class => lambda { |*| RepresentingSong }
|
32
|
-
end
|
33
|
-
|
34
|
-
it "creates fresh instance and doesn't extend" do
|
35
|
-
song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song
|
36
|
-
song.must_be_instance_of RepresentingSong
|
37
|
-
song.name.must_equal "Captured"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
# this throws a DeserializationError now.
|
43
|
-
describe "lambda { nil }" do
|
44
|
-
representer! do
|
45
|
-
property :title, :class => nil
|
46
|
-
end
|
47
|
-
|
48
|
-
it do
|
49
|
-
assert_raises Representable::DeserializeError do
|
50
|
-
OpenStruct.new.extend(representer).from_hash({"title" => {}})
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
describe "lambda receiving fragment and args" do
|
57
|
-
let (:klass) { Class.new do
|
58
|
-
class << self
|
59
|
-
attr_accessor :args
|
60
|
-
end
|
61
|
-
|
62
|
-
def from_hash(*)
|
63
|
-
self.class.new
|
64
|
-
end
|
65
|
-
end }
|
66
|
-
|
67
|
-
representer!(:inject => :klass) do
|
68
|
-
_klass = klass
|
69
|
-
property :song, :class => lambda { |fragment, args| _klass.args=([fragment,args]); _klass }
|
70
|
-
end
|
71
|
-
|
72
|
-
it { representer.prepare(OpenStruct.new).from_hash({"song" => {"name" => "Captured"}}, :volume => true).song.class.args.
|
73
|
-
must_equal([{"name"=>"Captured"}, {:volume=>true}]) }
|
74
|
-
end
|
75
|
-
|
76
|
-
|
77
|
-
describe "collection: lambda receiving fragment and args" do
|
78
|
-
let (:klass) { Class.new do
|
79
|
-
class << self
|
80
|
-
attr_accessor :args
|
81
|
-
end
|
82
|
-
|
83
|
-
def from_hash(*)
|
84
|
-
self.class.new
|
85
|
-
end
|
86
|
-
end }
|
87
|
-
|
88
|
-
representer!(:inject => :klass) do
|
89
|
-
_klass = klass
|
90
|
-
collection :songs, :class => lambda { |fragment, i, args| _klass.args=([fragment,i,args]); _klass }
|
91
|
-
end
|
92
|
-
|
93
|
-
it { representer.prepare(OpenStruct.new).from_hash({"songs" => [{"name" => "Captured"}]}, :volume => true).songs.first.class.args.
|
94
|
-
must_equal([{"name"=>"Captured"}, 0, {:volume=>true}]) }
|
95
|
-
end
|
96
|
-
|
97
|
-
|
98
|
-
describe "class: implementing #from_hash" do
|
99
|
-
let(:parser) do
|
100
|
-
Class.new do
|
101
|
-
def from_hash(*)
|
102
|
-
[1,2,3,4]
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
representer!(:inject => :parser) do
|
108
|
-
property :song, :class => parser # supposed this class exposes #from_hash itself.
|
109
|
-
end
|
110
|
-
|
111
|
-
it "allows returning arbitrary objects in #from_hash" do
|
112
|
-
representer.prepare(OpenStruct.new).from_hash({"song" => 1}).song.must_equal [1,2,3,4]
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
#TODO: test fragment,
|
118
|
-
|
119
|
-
# `class: Song` only, no :extend.
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'representable/coercion'
|
3
|
-
|
4
|
-
class VirtusCoercionTest < MiniTest::Spec
|
5
|
-
representer! do
|
6
|
-
include Representable::Coercion
|
7
|
-
|
8
|
-
property :title # no coercion.
|
9
|
-
property :length, :type => Float
|
10
|
-
|
11
|
-
property :band, :class => OpenStruct do
|
12
|
-
property :founded, :type => Integer
|
13
|
-
end
|
14
|
-
|
15
|
-
collection :songs, :class => OpenStruct do
|
16
|
-
property :ok, :type => Virtus::Attribute::Boolean
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
let (:album) { OpenStruct.new(:title => "Dire Straits", :length => 41.34,
|
21
|
-
:band => OpenStruct.new(:founded => "1977"),
|
22
|
-
:songs => [OpenStruct.new(:ok => 1), OpenStruct.new(:ok => 0)]) }
|
23
|
-
|
24
|
-
it { album.extend(representer).to_hash.must_equal({"title"=>"Dire Straits", "length"=>41.34, "band"=>{"founded"=>1977}, "songs"=>[{"ok"=>true}, {"ok"=>false}]}) }
|
25
|
-
|
26
|
-
it {
|
27
|
-
album = OpenStruct.new
|
28
|
-
album.extend(representer)
|
29
|
-
album.from_hash({"title"=>"Dire Straits", "length"=>"41.34", "band"=>{"founded"=>"1977"}, "songs"=>[{"ok"=>1}, {"ok"=>0}]})
|
30
|
-
|
31
|
-
# it
|
32
|
-
album.length.must_equal 41.34
|
33
|
-
album.band.founded.must_equal 1977
|
34
|
-
album.songs[0].ok.must_equal true
|
35
|
-
}
|
36
|
-
|
37
|
-
|
38
|
-
describe "with user :parse_filter and :render_filter" do
|
39
|
-
representer! do
|
40
|
-
include Representable::Coercion
|
41
|
-
|
42
|
-
property :length, :type => Float,
|
43
|
-
:parse_filter => lambda { |input, options| "#{input}.1" }, # happens BEFORE coercer.
|
44
|
-
:render_filter => lambda { |fragment,*| "#{fragment}.1" }
|
45
|
-
end
|
46
|
-
|
47
|
-
# user's :parse_filter(s) are run before coercion.
|
48
|
-
it { OpenStruct.new.extend(representer).from_hash("length"=>"1").length.must_equal 1.1 }
|
49
|
-
# user's :render_filter(s) are run before coercion.
|
50
|
-
it { OpenStruct.new(:length=>1).extend(representer).to_hash.must_equal({"length" => 1.1}) }
|
51
|
-
end
|
52
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
# tests defining representers in modules, decorators and classes and the inheritance when combined.
|
4
|
-
|
5
|
-
class ConfigInheritTest < MiniTest::Spec
|
6
|
-
def assert_cloned(child, parent, property)
|
7
|
-
child_def = child.representable_attrs.get(property)
|
8
|
-
parent_def = parent.representable_attrs.get(property)
|
9
|
-
|
10
|
-
child_def.merge!(:alias => property)
|
11
|
-
|
12
|
-
child_def[:alias].wont_equal parent_def[:alias]
|
13
|
-
child_def.object_id.wont_equal parent_def.object_id
|
14
|
-
end
|
15
|
-
# class Object
|
16
|
-
|
17
|
-
# end
|
18
|
-
module GenreModule
|
19
|
-
include Representable::Hash
|
20
|
-
property :genre
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
# in Decorator ------------------------------------------------
|
25
|
-
class Decorator < Representable::Decorator
|
26
|
-
include Representable::Hash
|
27
|
-
property :title
|
28
|
-
property :artist do
|
29
|
-
property :id
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
it { Decorator.definitions.keys.must_equal ["title", "artist"] }
|
34
|
-
|
35
|
-
# in inheriting Decorator
|
36
|
-
|
37
|
-
class InheritingDecorator < Decorator
|
38
|
-
property :location
|
39
|
-
end
|
40
|
-
|
41
|
-
it { InheritingDecorator.definitions.keys.must_equal ["title", "artist", "location"] }
|
42
|
-
it { assert_cloned(InheritingDecorator, Decorator, "title") }
|
43
|
-
it do
|
44
|
-
InheritingDecorator.representable_attrs.get(:artist).representer_module.object_id.wont_equal Decorator.representable_attrs.get(:artist).representer_module.object_id
|
45
|
-
end
|
46
|
-
|
47
|
-
# in inheriting and including Decorator
|
48
|
-
|
49
|
-
class InheritingAndIncludingDecorator < Decorator
|
50
|
-
include GenreModule
|
51
|
-
property :location
|
52
|
-
end
|
53
|
-
|
54
|
-
it { InheritingAndIncludingDecorator.definitions.keys.must_equal ["title", "artist", "genre", "location"] }
|
55
|
-
it { assert_cloned(InheritingAndIncludingDecorator, GenreModule, :genre) }
|
56
|
-
|
57
|
-
|
58
|
-
# in module ---------------------------------------------------
|
59
|
-
module Module
|
60
|
-
include Representable
|
61
|
-
property :title
|
62
|
-
end
|
63
|
-
|
64
|
-
it { Module.definitions.keys.must_equal ["title"] }
|
65
|
-
|
66
|
-
|
67
|
-
# in module including module
|
68
|
-
module SubModule
|
69
|
-
include Representable
|
70
|
-
include Module
|
71
|
-
|
72
|
-
property :location
|
73
|
-
end
|
74
|
-
|
75
|
-
it { SubModule.definitions.keys.must_equal ["title", "location"] }
|
76
|
-
it { assert_cloned(SubModule, Module, :title) }
|
77
|
-
|
78
|
-
# including preserves order
|
79
|
-
module IncludingModule
|
80
|
-
include Representable
|
81
|
-
property :genre
|
82
|
-
include Module
|
83
|
-
|
84
|
-
property :location
|
85
|
-
end
|
86
|
-
|
87
|
-
it { IncludingModule.definitions.keys.must_equal ["genre", "title", "location"] }
|
88
|
-
|
89
|
-
|
90
|
-
# included in class -------------------------------------------
|
91
|
-
class Class
|
92
|
-
include Representable
|
93
|
-
include IncludingModule
|
94
|
-
end
|
95
|
-
|
96
|
-
it { Class.definitions.keys.must_equal ["genre", "title", "location"] }
|
97
|
-
it { assert_cloned(Class, IncludingModule, :title) }
|
98
|
-
it { assert_cloned(Class, IncludingModule, :location) }
|
99
|
-
it { assert_cloned(Class, IncludingModule, :genre) }
|
100
|
-
|
101
|
-
# included in class with order
|
102
|
-
class DefiningClass
|
103
|
-
include Representable
|
104
|
-
property :street_cred
|
105
|
-
include IncludingModule
|
106
|
-
end
|
107
|
-
|
108
|
-
it { DefiningClass.definitions.keys.must_equal ["street_cred", "genre", "title", "location"] }
|
109
|
-
|
110
|
-
# in class
|
111
|
-
class RepresenterClass
|
112
|
-
include Representable
|
113
|
-
property :title
|
114
|
-
end
|
115
|
-
|
116
|
-
it { RepresenterClass.definitions.keys.must_equal ["title"] }
|
117
|
-
|
118
|
-
|
119
|
-
# in inheriting class
|
120
|
-
class InheritingClass < RepresenterClass
|
121
|
-
include Representable
|
122
|
-
property :location
|
123
|
-
end
|
124
|
-
|
125
|
-
it { InheritingClass.definitions.keys.must_equal ["title", "location"] }
|
126
|
-
it { assert_cloned(InheritingClass, RepresenterClass, :title) }
|
127
|
-
|
128
|
-
# in inheriting class and including
|
129
|
-
class InheritingAndIncludingClass < RepresenterClass
|
130
|
-
property :location
|
131
|
-
include GenreModule
|
132
|
-
end
|
133
|
-
|
134
|
-
it { InheritingAndIncludingClass.definitions.keys.must_equal ["title", "location", "genre"] }
|
135
|
-
end
|
@@ -1,122 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class ConfigTest < MiniTest::Spec
|
4
|
-
subject { Representable::Config.new(Representable::Definition) }
|
5
|
-
PunkRock = Class.new
|
6
|
-
Definition = Representable::Definition
|
7
|
-
|
8
|
-
describe "wrapping" do
|
9
|
-
it "returns false per default" do
|
10
|
-
assert_equal nil, subject.wrap_for("Punk", nil)
|
11
|
-
end
|
12
|
-
|
13
|
-
# it "infers a printable class name if set to true" do
|
14
|
-
# subject.wrap = true
|
15
|
-
# assert_equal "punk_rock", subject.wrap_for(PunkRock, nil)
|
16
|
-
# end
|
17
|
-
|
18
|
-
# it "can be set explicitely" do
|
19
|
-
# subject.wrap = "Descendents"
|
20
|
-
# assert_equal "Descendents", subject.wrap_for(PunkRock, nil)
|
21
|
-
# end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "#[]" do
|
25
|
-
# does return nil for non-existent
|
26
|
-
it { subject[:hello].must_equal nil }
|
27
|
-
end
|
28
|
-
|
29
|
-
# describe "#[]" do
|
30
|
-
# before { subject.add(:title, {:me => true}) }
|
31
|
-
|
32
|
-
# it { subject[:unknown].must_equal nil }
|
33
|
-
# it { subject.get(:title)[:me].must_equal true }
|
34
|
-
# it { subject["title"][:me].must_equal true }
|
35
|
-
# end
|
36
|
-
|
37
|
-
# []=
|
38
|
-
# []=(... inherit: true)
|
39
|
-
# forwarded to Config#definitions
|
40
|
-
# that goes to ConfigDefinitionsTest
|
41
|
-
describe "#add" do
|
42
|
-
describe "returns" do
|
43
|
-
it do
|
44
|
-
# #add returns Definition.`
|
45
|
-
subject = Representable::Config.new(Representable::Definition).add(:title, {:me => true})
|
46
|
-
|
47
|
-
subject.must_be_kind_of Representable::Definition
|
48
|
-
subject[:me].must_equal true
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
before { subject.add(:title, {:me => true}) }
|
53
|
-
|
54
|
-
# must be kind of Definition
|
55
|
-
it { subject.size.must_equal 1 }
|
56
|
-
it { subject.get(:title).name.must_equal "title" }
|
57
|
-
it { subject.get(:title)[:me].must_equal true }
|
58
|
-
|
59
|
-
# this is actually tested in context in inherit_test.
|
60
|
-
it "overrides former definition" do
|
61
|
-
subject.add(:title, {:peer => Module})
|
62
|
-
subject.get(:title)[:me].must_equal nil
|
63
|
-
subject.get(:title)[:peer].must_equal Module
|
64
|
-
end
|
65
|
-
|
66
|
-
describe "inherit: true" do
|
67
|
-
before {
|
68
|
-
subject.add(:title, {:me => true})
|
69
|
-
subject.add(:title, {:peer => Module, :inherit => true})
|
70
|
-
}
|
71
|
-
|
72
|
-
it { subject.get(:title)[:me].must_equal true }
|
73
|
-
it { subject.get(:title)[:peer].must_equal Module }
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
describe "#remove" do
|
79
|
-
subject { Representable::Config.new(Representable::Definition) }
|
80
|
-
|
81
|
-
it do
|
82
|
-
subject.add(:title, {:me => true})
|
83
|
-
subject.add(:genre, {})
|
84
|
-
subject.get(:genre).must_be_kind_of Representable::Definition
|
85
|
-
|
86
|
-
subject.remove(:genre)
|
87
|
-
subject.get(:genre).must_equal nil
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
describe "#each" do
|
93
|
-
before { subject.add(:title, {:me => true}) }
|
94
|
-
|
95
|
-
it "what" do
|
96
|
-
definitions = []
|
97
|
-
subject.each { |dfn| definitions << dfn }
|
98
|
-
definitions.size.must_equal 1
|
99
|
-
definitions[0][:me].must_equal true
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
describe "#options" do
|
104
|
-
it { subject.options.must_equal({}) }
|
105
|
-
it do
|
106
|
-
subject.options[:namespacing] = true
|
107
|
-
subject.options[:namespacing].must_equal true
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
describe "#get" do
|
112
|
-
subject { Representable::Config.new(Representable::Definition) }
|
113
|
-
|
114
|
-
it do
|
115
|
-
title = subject.add(:title, {})
|
116
|
-
length = subject.add(:length, {})
|
117
|
-
|
118
|
-
subject.get(:title).must_equal title
|
119
|
-
subject.get(:length).must_equal length
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
# TODO: remove in 2.0.
|
4
|
-
class DecoratorScopeTest < MiniTest::Spec
|
5
|
-
representer! do
|
6
|
-
property :title, :getter => lambda { |*| title_from_representer }, :decorator_scope => true
|
7
|
-
end
|
8
|
-
|
9
|
-
let (:representer_with_method) {
|
10
|
-
Module.new do
|
11
|
-
include Representable::Hash
|
12
|
-
property :title, :decorator_scope => true
|
13
|
-
def title; "Crystal Planet"; end
|
14
|
-
end
|
15
|
-
}
|
16
|
-
|
17
|
-
it "executes lambdas in represented context" do
|
18
|
-
Class.new do
|
19
|
-
def title_from_representer
|
20
|
-
"Sounds Of Silence"
|
21
|
-
end
|
22
|
-
end.new.extend(representer).to_hash.must_equal({"title"=>"Sounds Of Silence"})
|
23
|
-
end
|
24
|
-
|
25
|
-
it "executes method in represented context" do
|
26
|
-
Object.new.extend(representer_with_method).to_hash.must_equal({"title"=>"Crystal Planet"})
|
27
|
-
end
|
28
|
-
end
|