trailer_vote-media_types 0.5.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 +7 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +20 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +48 -0
- data/README.md +105 -0
- data/Rakefile +12 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/lib/trailer_vote/media_types.rb +32 -0
- data/lib/trailer_vote/media_types/audio_fragment.rb +106 -0
- data/lib/trailer_vote/media_types/base_text.rb +22 -0
- data/lib/trailer_vote/media_types/carousel.rb +42 -0
- data/lib/trailer_vote/media_types/client_configuration.rb +34 -0
- data/lib/trailer_vote/media_types/configuration.rb +54 -0
- data/lib/trailer_vote/media_types/errors.rb +31 -0
- data/lib/trailer_vote/media_types/feedback.rb +49 -0
- data/lib/trailer_vote/media_types/feedback_listing.rb +48 -0
- data/lib/trailer_vote/media_types/fingerprint_binary.rb +67 -0
- data/lib/trailer_vote/media_types/interactive_player.rb +33 -0
- data/lib/trailer_vote/media_types/partials/image_links.rb +20 -0
- data/lib/trailer_vote/media_types/persona.rb +38 -0
- data/lib/trailer_vote/media_types/place.rb +105 -0
- data/lib/trailer_vote/media_types/product.rb +175 -0
- data/lib/trailer_vote/media_types/product_image.rb +113 -0
- data/lib/trailer_vote/media_types/product_lookup.rb +37 -0
- data/lib/trailer_vote/media_types/product_place_link.rb +37 -0
- data/lib/trailer_vote/media_types/product_video.rb +92 -0
- data/lib/trailer_vote/media_types/products_listing.rb +42 -0
- data/lib/trailer_vote/media_types/sentiment_feedback.rb +97 -0
- data/lib/trailer_vote/media_types/types/boolean.rb +15 -0
- data/lib/trailer_vote/media_types/types/product_data_type.rb +15 -0
- data/lib/trailer_vote/media_types/types/product_image_type.rb +25 -0
- data/lib/trailer_vote/media_types/types/product_movie_handler.rb +15 -0
- data/lib/trailer_vote/media_types/types/product_movie_type.rb +17 -0
- data/lib/trailer_vote/media_types/types/uuid_v4.rb +10 -0
- data/lib/trailer_vote/media_types/types/vote_value.rb +17 -0
- data/lib/trailer_vote/media_types/version.rb +7 -0
- data/trailer_vote-media_types.gemspec +32 -0
- metadata +209 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: f98f974c07395747cb305e0decfbba8f0b55c01b
|
|
4
|
+
data.tar.gz: b7d07d6dcada4d6d0a07e3423cfe555119705570
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a9b902007b4417d44a04ebc12986e7eec12d05bac6a451771dfe7f1b1d36454497b67d19a8d24c46a0fb6eedef5ae5149c7d9a148a236657ef620c4829970cdf
|
|
7
|
+
data.tar.gz: f74410cc2caedb5f3d74cb8de32020421b5a0d6208016c45c95bb39c0d550f6d3cd2d0dd5472e608c4b2acf16945fa86ebdf8b29f5bd780582772795094ffe8a
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
Include:
|
|
3
|
+
- '**/Rakefile'
|
|
4
|
+
- 'lib/**/*.rb'
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'Gemfile'
|
|
7
|
+
- 'bin/**/*'
|
|
8
|
+
TargetRubyVersion: 2.3
|
|
9
|
+
|
|
10
|
+
Layout/EmptyLinesAroundClassBody:
|
|
11
|
+
Enabled: false
|
|
12
|
+
|
|
13
|
+
Layout/EndOfLine:
|
|
14
|
+
Enabled: false
|
|
15
|
+
|
|
16
|
+
Metrics/LineLength:
|
|
17
|
+
Max: 120
|
|
18
|
+
|
|
19
|
+
Metrics/MethodLength:
|
|
20
|
+
Max: 15
|
|
21
|
+
|
|
22
|
+
Style/Documentation:
|
|
23
|
+
Enabled: false
|
|
24
|
+
|
|
25
|
+
Style/EmptyMethod:
|
|
26
|
+
EnforcedStyle: expanded
|
|
27
|
+
|
|
28
|
+
Style/IfUnlessModifier:
|
|
29
|
+
Enabled: false
|
data/.travis.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
sudo: false
|
|
2
|
+
language: ruby
|
|
3
|
+
cache: bundler
|
|
4
|
+
rvm:
|
|
5
|
+
- 2.3.0
|
|
6
|
+
- 2.4
|
|
7
|
+
- 2.5
|
|
8
|
+
- 2.6
|
|
9
|
+
- rbx-3
|
|
10
|
+
- ruby-head
|
|
11
|
+
matrix:
|
|
12
|
+
allow_failures:
|
|
13
|
+
- rvm: ruby-head
|
|
14
|
+
- rvm: rbx-3
|
|
15
|
+
- rvm: 2.6
|
|
16
|
+
before_install:
|
|
17
|
+
- gem update --system
|
|
18
|
+
- gem --version
|
|
19
|
+
install:
|
|
20
|
+
- bundle install --with development --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
trailer_vote-media_types (0.5.0)
|
|
5
|
+
media_types (~> 0.5.5)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
ansi (1.5.0)
|
|
11
|
+
awesome_print (1.8.0)
|
|
12
|
+
builder (3.2.3)
|
|
13
|
+
docile (1.3.1)
|
|
14
|
+
json (2.1.0)
|
|
15
|
+
media_types (0.5.5)
|
|
16
|
+
minitest (5.11.3)
|
|
17
|
+
minitest-ci (3.4.0)
|
|
18
|
+
minitest (>= 5.0.6)
|
|
19
|
+
minitest-reporters (1.3.1)
|
|
20
|
+
ansi
|
|
21
|
+
builder
|
|
22
|
+
minitest (>= 5.0)
|
|
23
|
+
ruby-progressbar
|
|
24
|
+
oj (3.6.11)
|
|
25
|
+
rake (10.5.0)
|
|
26
|
+
ruby-progressbar (1.9.0)
|
|
27
|
+
simplecov (0.16.1)
|
|
28
|
+
docile (~> 1.1)
|
|
29
|
+
json (>= 1.8, < 3)
|
|
30
|
+
simplecov-html (~> 0.10.0)
|
|
31
|
+
simplecov-html (0.10.2)
|
|
32
|
+
|
|
33
|
+
PLATFORMS
|
|
34
|
+
x64-mingw32
|
|
35
|
+
|
|
36
|
+
DEPENDENCIES
|
|
37
|
+
awesome_print
|
|
38
|
+
bundler (~> 1.16)
|
|
39
|
+
minitest (~> 5.0)
|
|
40
|
+
minitest-ci
|
|
41
|
+
minitest-reporters
|
|
42
|
+
oj
|
|
43
|
+
rake (~> 10.0)
|
|
44
|
+
simplecov
|
|
45
|
+
trailer_vote-media_types!
|
|
46
|
+
|
|
47
|
+
BUNDLED WITH
|
|
48
|
+
1.16.5
|
data/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# TrailerVote::MediaTypes
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
Add this line to your application's Gemfile:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem 'trailer_vote-media_types'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install trailer_vote-media_types
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
This gem is based on [`media_types`](https://github.com/SleeplessByte/media-types-ruby), and inherits all the functionality from that gem. In general, you can use `trailer_vote-media_types` in the following ways:
|
|
22
|
+
|
|
23
|
+
### `Accept` and `Content-Type` construction
|
|
24
|
+
|
|
25
|
+
All the media types can be turned into a header value, both acceptable for the `Accept` and `Content-Type` headers:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
TrailerVote::MediaTypes::Configuration.to_constructable.version(2).to_s
|
|
29
|
+
# => "application/vnd.trailervote.configuration.v2+json"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The [`Constructable`](https://www.rubydoc.info/gems/media_types/MediaTypes/Constructable) result of `#to_constructable` allows for chaining the various options in our media types, namely `view` (e.g. `index`, `create`) and `version`. The default `suffix` is none for binary types and `json` for text type, as we don't support XML at the moment.
|
|
33
|
+
|
|
34
|
+
It is **recommended** to always include `.version(n)`, otherwise you will get the newest version, which might not be what you support in the code.
|
|
35
|
+
|
|
36
|
+
### `Accept` with quality parameter
|
|
37
|
+
|
|
38
|
+
If you want to have multiple `Accept` values, but with different priorities, `Constructable#to_s` accepts an additional parameter to set this. For example, if you want to make a fully qualified `Accept` header and you prefer product version 2, but accept version 1 as well:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
[
|
|
42
|
+
TrailerVote::MediaTypes::Product.to_constructable.version(2).to_s,
|
|
43
|
+
TrailerVote::MediaTypes::Product.to_constructable.version(1).to_s(0.9),
|
|
44
|
+
TrailerVote::MediaTypes::Errors.to_constructable.version(1).to_s(0.1)
|
|
45
|
+
].join(', ')
|
|
46
|
+
# => "application/vnd.trailervote.configuration.product.v2+json,
|
|
47
|
+
# application/vnd.trailervote.configuration.product.v1+json; q=0.9,
|
|
48
|
+
# application/vnd.trailervote.errors.v1+json; q=0.1"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Data validation
|
|
52
|
+
|
|
53
|
+
All the media types come equipped with validations, for most known / expected permutations of views and versions. Validations are based on [`Scheme`](https://www.rubydoc.info/gems/media_types/MediaTypes/Scheme) from the [`media_types`](https://github.com/SleeplessByte/media-types-ruby) gem.
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
media_type = TrailerVote::MediaTypes::ClientConfiguration.to_constructable.version(1)
|
|
57
|
+
# => application/vnd.trailervote.client_configuration.v1+json
|
|
58
|
+
|
|
59
|
+
media_type.valid?(
|
|
60
|
+
configuration: {
|
|
61
|
+
place: "https://fake.trailervote.com/api/places/435ec0b8-100d-4e92-8a17-04bc77e90880",
|
|
62
|
+
persona: "https://fake.trailervote.com/api/persona/e03a1c72-8bce-408c-a6e4-de215e73dd92"
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
# => true
|
|
66
|
+
|
|
67
|
+
media_type.validate!(
|
|
68
|
+
configuration: {
|
|
69
|
+
place: "https://fake.trailervote.com/api/places/435ec0b8-100d-4e92-8a17-04bc77e90880",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
# => raises MediaTypes::Scheme::ExhaustedOutputError: Missing keys in output: [:persona] at [.->configuration]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `Mime::Type` registration
|
|
76
|
+
|
|
77
|
+
In case you use `action_dispatch/http/mime_type`, you may register the TrailerVote Media Types into the `Mime::Type` registry like so:
|
|
78
|
+
```ruby
|
|
79
|
+
TrailerVote::MediaTypes::Persona.register
|
|
80
|
+
# => [...] # Array of registered types, each version, each suffix permutation, all aliases
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
You can now look them up in the registry:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
media_type = TrailerVote::MediaTypes::Persona.to_constructable.version(1).to_s
|
|
87
|
+
# => "application/vnd.trailervote.persona.v1+json"
|
|
88
|
+
|
|
89
|
+
Mime::Type.lookup(media_type)
|
|
90
|
+
# => Mime::Type entry
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
|
|
96
|
+
also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
97
|
+
|
|
98
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
|
99
|
+
version number in `version.rb`, and then run `bundle update trailer_vote-media_types` in any repository that depends on
|
|
100
|
+
this gem. If you have permissions, you may call `bundle exec rake release` to create a new git tag, push
|
|
101
|
+
git commits and tags, and push the `.gem` file to the rubygems gem server.
|
|
102
|
+
|
|
103
|
+
## Contributing
|
|
104
|
+
|
|
105
|
+
Bug reports and pull requests are welcome on GitHub at [TrailerVote/trailervote-media-types](https://github.com/TrailerVote/trailervote-media-types)
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'trailer_vote/media_types'
|
|
6
|
+
require 'awesome_print'
|
|
7
|
+
|
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
10
|
+
|
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
12
|
+
# require "pry"
|
|
13
|
+
# Pry.start
|
|
14
|
+
|
|
15
|
+
require 'irb'
|
|
16
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'media_types'
|
|
4
|
+
require_relative './media_types/version'
|
|
5
|
+
|
|
6
|
+
require_relative './media_types/base_text'
|
|
7
|
+
require_relative './media_types/audio_fragment'
|
|
8
|
+
require_relative './media_types/configuration'
|
|
9
|
+
require_relative './media_types/client_configuration'
|
|
10
|
+
require_relative './media_types/errors'
|
|
11
|
+
require_relative './media_types/fingerprint_binary'
|
|
12
|
+
require_relative './media_types/product_lookup'
|
|
13
|
+
require_relative './media_types/sentiment_feedback'
|
|
14
|
+
require_relative './media_types/carousel'
|
|
15
|
+
require_relative './media_types/feedback_listing'
|
|
16
|
+
require_relative './media_types/interactive_player'
|
|
17
|
+
require_relative './media_types/products_listing'
|
|
18
|
+
require_relative './media_types/feedback'
|
|
19
|
+
require_relative './media_types/persona'
|
|
20
|
+
require_relative './media_types/place'
|
|
21
|
+
require_relative './media_types/product'
|
|
22
|
+
require_relative './media_types/product_image'
|
|
23
|
+
require_relative './media_types/product_place_link'
|
|
24
|
+
require_relative './media_types/product_video'
|
|
25
|
+
|
|
26
|
+
module TrailerVote
|
|
27
|
+
module MediaTypes
|
|
28
|
+
INDEX_VIEW = ::MediaTypes::INDEX_VIEW
|
|
29
|
+
COLLECTION_VIEW = ::MediaTypes::COLLECTION_VIEW
|
|
30
|
+
CREATE_VIEW = ::MediaTypes::CREATE_VIEW
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './base_text'
|
|
4
|
+
|
|
5
|
+
module TrailerVote
|
|
6
|
+
module MediaTypes
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Media Types for Audio Fragments
|
|
10
|
+
#
|
|
11
|
+
# Audio Fragments are recognizable using {FingerprintBinary}. It might be an advertisement, a trailer or a sound
|
|
12
|
+
# bite. They belong to a {Product}. Audio fragments are used for recognition, e.g. showing an advertisement when
|
|
13
|
+
# it's recognised, and used for feedback, i.e. a requirement to record and store {Feedback}.
|
|
14
|
+
#
|
|
15
|
+
class AudioFragment < BaseText
|
|
16
|
+
|
|
17
|
+
# @!method to_constructable
|
|
18
|
+
# Returns the construtable media type
|
|
19
|
+
#
|
|
20
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Constructable ::MediaTypes::Constructable
|
|
21
|
+
# @return [::MediaTypes::Constructable] a constructable
|
|
22
|
+
#
|
|
23
|
+
media_type 'audio_fragment', defaults: { suffix: :json, version: 1 }
|
|
24
|
+
|
|
25
|
+
# @!method valid?(data, constructed_media_type, **opts)
|
|
26
|
+
# Validates the +data+ against the validation for +constructed_media_type+.
|
|
27
|
+
#
|
|
28
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Scheme#valid%3F-instance_method ::MediaTypes::Scheme#valid?
|
|
29
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Constructable#valid%3F-instance_method ::MediaTypes::Constructable#valid?
|
|
30
|
+
#
|
|
31
|
+
# @param [Object] data the data to validate
|
|
32
|
+
# @param [Constructable, String] constructed_media_type something that resolved into a media type with validations
|
|
33
|
+
# @param [Hash] opts passed on to {::MediaTypes::Scheme#valid?}
|
|
34
|
+
#
|
|
35
|
+
# @return [TrueClass, FalseClass] true if valid, false otherwise
|
|
36
|
+
#
|
|
37
|
+
# @!method validate!(data, constructed_media_type, **opts)
|
|
38
|
+
# Validates the +data+ against the validation for +constructed_media_type+. Raises if invalid
|
|
39
|
+
#
|
|
40
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Scheme#validate-instance_method ::MediaTypes::Scheme#validate
|
|
41
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Constructable#validate!-instance_method ::MediaTypes::Constructable#validate!
|
|
42
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Scheme/ValidationError ::MediaTypes::Scheme::ValidationError
|
|
43
|
+
#
|
|
44
|
+
# @param [Object] data the data to validate
|
|
45
|
+
# @param [Constructable, String] constructed_media_type something that resolved into a media type with validations
|
|
46
|
+
# @param [Hash] opts passed on to {::MediaTypes::Scheme#validate}
|
|
47
|
+
#
|
|
48
|
+
# @raise ::MediaTypes::Scheme::ValidationError an error if the scheme is not valid
|
|
49
|
+
# @return [TrueClass] true if valid
|
|
50
|
+
#
|
|
51
|
+
validations do
|
|
52
|
+
version 1 do
|
|
53
|
+
version_1_base = ::MediaTypes::Scheme.new do
|
|
54
|
+
attribute :content_addressable, String
|
|
55
|
+
attribute :deleted_at, AllowNil(String)
|
|
56
|
+
|
|
57
|
+
link :self
|
|
58
|
+
link :product
|
|
59
|
+
link :feedback
|
|
60
|
+
link :advert
|
|
61
|
+
link :direct, optional: true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
attribute :audio_fragment do
|
|
65
|
+
merge version_1_base
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
view 'index' do
|
|
69
|
+
attribute :audio_fragments do
|
|
70
|
+
collection :_index, allow_empty: true do
|
|
71
|
+
attribute :href, String
|
|
72
|
+
not_strict
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
not_strict
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
view 'collection' do
|
|
80
|
+
attribute :audio_fragments do
|
|
81
|
+
collection :_embedded, version_1_base
|
|
82
|
+
not_strict
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @!method register
|
|
89
|
+
# Registers all the known permutations of versions, views, suffixes and aliases for this media type via
|
|
90
|
+
# {::MediaTypes.register}
|
|
91
|
+
#
|
|
92
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes#register-class-method ::Mediatypes.register
|
|
93
|
+
# @see https://www.rubydoc.info/gems/media_types/MediaTypes/Registerable ::Mediatypes::Registerable
|
|
94
|
+
#
|
|
95
|
+
# @return [Array<::MediaTypes::Registerable>]
|
|
96
|
+
registrations :audio_fragment do
|
|
97
|
+
view 'index', :audio_fragment_urls
|
|
98
|
+
view 'collection', :audio_fragments
|
|
99
|
+
|
|
100
|
+
versions 1
|
|
101
|
+
|
|
102
|
+
type_alias 'audio-fragment'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'media_types'
|
|
4
|
+
|
|
5
|
+
module TrailerVote
|
|
6
|
+
module MediaTypes
|
|
7
|
+
class BaseText
|
|
8
|
+
include ::MediaTypes::Dsl
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
BASE_TEXT_FORMAT = 'application/vnd.trailervote.%<type>s.v%<version>s.%<view>s+%<suffix>s'
|
|
15
|
+
|
|
16
|
+
def base_format
|
|
17
|
+
BASE_TEXT_FORMAT
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|