riddler 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +105 -6
- data/lib/riddler.rb +52 -3
- data/lib/riddler/configuration.rb +11 -0
- data/lib/riddler/context.rb +40 -2
- data/lib/riddler/context_builder.rb +35 -0
- data/lib/riddler/context_builders/faraday_builder.rb +23 -0
- data/lib/riddler/context_builders/user_agent.rb +34 -0
- data/lib/riddler/context_director.rb +67 -0
- data/lib/riddler/drops/hash_drop.rb +29 -0
- data/lib/riddler/element.rb +15 -14
- data/lib/riddler/elements/heading.rb +0 -1
- data/lib/riddler/elements/image.rb +21 -0
- data/lib/riddler/elements/link.rb +30 -0
- data/lib/riddler/elements/text.rb +17 -0
- data/lib/riddler/elements/variant.rb +21 -0
- data/lib/riddler/includeable.rb +19 -0
- data/lib/riddler/protobuf/content_definition.proto +51 -0
- data/lib/riddler/protobuf/content_definition_pb.rb +33 -0
- data/lib/riddler/protobuf/content_management.proto +35 -0
- data/lib/riddler/protobuf/content_management_pb.rb +43 -0
- data/lib/riddler/protobuf/content_management_services_pb.rb +27 -0
- data/lib/riddler/protobuf/slug.proto +61 -0
- data/lib/riddler/protobuf/slug_pb.rb +52 -0
- data/lib/riddler/step.rb +16 -16
- data/lib/riddler/steps/content.rb +5 -5
- data/lib/riddler/steps/variant.rb +23 -0
- data/lib/riddler/test_generator.rb +73 -0
- data/lib/riddler/use_cases.rb +7 -0
- data/lib/riddler/use_cases/admin_preview_step.rb +32 -0
- data/lib/riddler/use_cases/complete_interaction.rb +20 -0
- data/lib/riddler/use_cases/dismiss_interaction.rb +20 -0
- data/lib/riddler/use_cases/preview_context.rb +28 -0
- data/lib/riddler/use_cases/preview_step.rb +16 -5
- data/lib/riddler/use_cases/show_content_definition.rb +42 -0
- data/lib/riddler/use_cases/show_slug.rb +138 -0
- data/lib/riddler/version.rb +1 -1
- data/riddler.gemspec +12 -8
- metadata +100 -10
- data/.gitignore +0 -8
- data/.travis.yml +0 -7
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -58
- data/Rakefile +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: riddler/protobuf/slug.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
require 'google/protobuf/timestamp_pb'
|
7
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
8
|
+
add_message "riddler.protobuf.Slug" do
|
9
|
+
optional :id, :string, 1
|
10
|
+
optional :created_at, :message, 2, "google.protobuf.Timestamp"
|
11
|
+
optional :updated_at, :message, 3, "google.protobuf.Timestamp"
|
12
|
+
optional :name, :string, 4
|
13
|
+
optional :status, :enum, 5, "riddler.protobuf.SlugStatus"
|
14
|
+
optional :content_definition_id, :string, 6
|
15
|
+
optional :interaction_identity, :string, 7
|
16
|
+
optional :target_predicate, :string, 8
|
17
|
+
end
|
18
|
+
add_message "riddler.protobuf.EventCount" do
|
19
|
+
optional :event_name, :string, 1
|
20
|
+
optional :count, :int32, 2
|
21
|
+
end
|
22
|
+
add_message "riddler.protobuf.SlugStats" do
|
23
|
+
optional :interval, :enum, 1, "riddler.protobuf.Interval"
|
24
|
+
repeated :event_counts, :message, 2, "riddler.protobuf.EventCount"
|
25
|
+
end
|
26
|
+
add_enum "riddler.protobuf.SlugStatus" do
|
27
|
+
value :UNKNOWN_SLUG_STATUS, 0
|
28
|
+
value :LIVE, 1
|
29
|
+
value :PAUSED, 2
|
30
|
+
end
|
31
|
+
add_enum "riddler.protobuf.Interval" do
|
32
|
+
value :UNKNOWN_INTERVAL, 0
|
33
|
+
value :SECOND, 1
|
34
|
+
value :MINUTE, 2
|
35
|
+
value :HOUR, 3
|
36
|
+
value :DAY, 4
|
37
|
+
value :WEEK, 5
|
38
|
+
value :MONTH, 6
|
39
|
+
value :QUARTER, 7
|
40
|
+
value :YEAR, 8
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Riddler
|
45
|
+
module Protobuf
|
46
|
+
Slug = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.Slug").msgclass
|
47
|
+
EventCount = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.EventCount").msgclass
|
48
|
+
SlugStats = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.SlugStats").msgclass
|
49
|
+
SlugStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.SlugStatus").enummodule
|
50
|
+
Interval = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.Interval").enummodule
|
51
|
+
end
|
52
|
+
end
|
data/lib/riddler/step.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
module Riddler
|
2
|
-
|
3
2
|
class Step
|
4
|
-
|
3
|
+
include ::Riddler::Includeable
|
4
|
+
|
5
|
+
attr_reader :definition, :context, :preview_enabled
|
6
|
+
|
7
|
+
def self.subclasses
|
8
|
+
@@subclasses ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inherited subclass
|
12
|
+
self.subclasses << subclass
|
13
|
+
end
|
5
14
|
|
6
15
|
def self.for definition, context
|
7
|
-
|
8
|
-
step_type = definition["object"]
|
16
|
+
step_type = definition["type"]
|
9
17
|
|
10
|
-
|
11
|
-
klass = subclasses.detect { |klass| klass.type == step_type }
|
18
|
+
klass = subclasses.detect { |k| k.type == step_type }
|
12
19
|
|
13
20
|
klass.new definition, context
|
14
21
|
end
|
@@ -18,20 +25,13 @@ module Riddler
|
|
18
25
|
@context = context
|
19
26
|
end
|
20
27
|
|
21
|
-
#def id
|
22
|
-
# definition["id"]
|
23
|
-
#end
|
24
|
-
|
25
|
-
#def type
|
26
|
-
# self.class.type
|
27
|
-
#end
|
28
|
-
|
29
28
|
def to_hash
|
30
29
|
{
|
30
|
+
content_type: "step",
|
31
31
|
type: self.class.type,
|
32
|
-
id: definition["id"]
|
32
|
+
id: definition["id"],
|
33
|
+
name: definition["name"]
|
33
34
|
}
|
34
35
|
end
|
35
36
|
end
|
36
|
-
|
37
37
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
module Riddler
|
2
2
|
module Steps
|
3
|
-
|
4
3
|
class Content < ::Riddler::Step
|
5
4
|
def self.type
|
6
5
|
"content"
|
7
6
|
end
|
8
7
|
|
9
8
|
def elements
|
10
|
-
@elements ||= definition["elements"]
|
11
|
-
|
12
|
-
|
9
|
+
@elements ||= definition["elements"]
|
10
|
+
.map do |element_definition|
|
11
|
+
::Riddler::Element.for element_definition, context
|
12
|
+
end
|
13
|
+
.select(&:include?)
|
13
14
|
end
|
14
15
|
|
15
16
|
def to_hash
|
@@ -20,6 +21,5 @@ module Riddler
|
|
20
21
|
hash
|
21
22
|
end
|
22
23
|
end
|
23
|
-
|
24
24
|
end
|
25
25
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Riddler
|
2
|
+
module Steps
|
3
|
+
class Variant < ::Riddler::Step
|
4
|
+
def self.type
|
5
|
+
"variant"
|
6
|
+
end
|
7
|
+
|
8
|
+
def steps
|
9
|
+
@steps ||= definition["steps"].map do |hash|
|
10
|
+
::Riddler::Step.for hash, context
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def included_step
|
15
|
+
steps.detect &:include?
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
included_step&.to_hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module ::Riddler
|
4
|
+
class TestGenerator
|
5
|
+
include ERB::Util
|
6
|
+
attr_reader :project_root, :input_filename, :test_case
|
7
|
+
|
8
|
+
def initialize project_root, input_filename
|
9
|
+
@project_root = project_root
|
10
|
+
@input_filename = input_filename
|
11
|
+
@test_case = YAML.safe_load File.read input_filename
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_case_name
|
15
|
+
test_case["name"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def class_name
|
19
|
+
classify test_case_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def definition
|
23
|
+
test_case["definition"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def tests
|
27
|
+
test_case["tests"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate
|
31
|
+
folder = input_filename.dirname.basename.to_s
|
32
|
+
output_filename = project_root.join *%W[ test cases #{folder} #{test_case_name}_test.rb ]
|
33
|
+
puts "Generating #{output_filename.relative_path_from(project_root)}"
|
34
|
+
output_filename.dirname.mkdir unless output_filename.dirname.exist?
|
35
|
+
File.write output_filename, render
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def render
|
41
|
+
ERB.new(test_class_template).result(binding)
|
42
|
+
end
|
43
|
+
|
44
|
+
def classify string
|
45
|
+
string.split('_').collect!{ |w| w.capitalize }.join
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_class_template
|
49
|
+
<<~TEMPLATE
|
50
|
+
require "test_helper"
|
51
|
+
|
52
|
+
class <%= class_name %>Test < ::Minitest::Test
|
53
|
+
attr_reader :definition
|
54
|
+
|
55
|
+
def setup
|
56
|
+
@definition = <%= definition %>
|
57
|
+
end
|
58
|
+
<% tests.each do |test| %>
|
59
|
+
def test_<%= test["name"] %>
|
60
|
+
expected_result = <% if test["result"].nil? %>nil<% else %><%= test["result"] %><% end %>
|
61
|
+
context = <% if test["context"].nil? %>nil<% else %><%= test["context"] %><% end %>
|
62
|
+
|
63
|
+
result = ::Riddler.render definition, context
|
64
|
+
result&.transform_keys! { |k| k.to_s }
|
65
|
+
|
66
|
+
assert_equal_hash expected_result, result
|
67
|
+
end
|
68
|
+
<% end %>
|
69
|
+
end
|
70
|
+
TEMPLATE
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require "riddler/use_cases/admin_preview_step"
|
2
|
+
require "riddler/use_cases/complete_interaction"
|
3
|
+
require "riddler/use_cases/dismiss_interaction"
|
4
|
+
require "riddler/use_cases/preview_context"
|
5
|
+
require "riddler/use_cases/preview_step"
|
6
|
+
require "riddler/use_cases/show_content_definition"
|
7
|
+
require "riddler/use_cases/show_slug"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class AdminPreviewStep
|
4
|
+
attr_reader :definition, :preview_context_data, :context, :step
|
5
|
+
|
6
|
+
def initialize definition, preview_context_data: {}
|
7
|
+
@definition = definition
|
8
|
+
@preview_context_data = preview_context_data
|
9
|
+
@context = ::Riddler::Context.new preview_context_data
|
10
|
+
@step = ::Riddler::Step.for definition, context
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
if step.include?
|
15
|
+
hash = step.to_hash
|
16
|
+
return hash unless hash.nil?
|
17
|
+
|
18
|
+
{
|
19
|
+
response_code: 204,
|
20
|
+
message: "There was no step to include"
|
21
|
+
}
|
22
|
+
else
|
23
|
+
{
|
24
|
+
response_code: 204,
|
25
|
+
include_predicate: step.include_predicate,
|
26
|
+
message: "Excluded - the include_predicate returned false"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class CompleteInteraction
|
4
|
+
attr_reader :interaction_repo, :interaction_id
|
5
|
+
|
6
|
+
def initialize interaction_repo:, interaction_id:
|
7
|
+
@interaction_repo = interaction_repo
|
8
|
+
@interaction_id = interaction_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
interaction = interaction_repo.find_by id: interaction_id
|
13
|
+
return if interaction.nil?
|
14
|
+
|
15
|
+
interaction.complete
|
16
|
+
interaction_repo.update interaction
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class DismissInteraction
|
4
|
+
attr_reader :interaction_repo, :interaction_id, :interaction
|
5
|
+
|
6
|
+
def initialize interaction_repo:, interaction_id:
|
7
|
+
@interaction_repo = interaction_repo
|
8
|
+
@interaction_id = interaction_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
@interaction = interaction_repo.find_by id: interaction_id
|
13
|
+
return if interaction.nil?
|
14
|
+
|
15
|
+
interaction.dismiss
|
16
|
+
interaction_repo.update interaction
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class PreviewContext
|
4
|
+
attr_reader :params, :headers
|
5
|
+
|
6
|
+
def initialize params: {}, headers: {}
|
7
|
+
@params = params
|
8
|
+
@headers = headers
|
9
|
+
end
|
10
|
+
|
11
|
+
def context
|
12
|
+
@context ||= generate_context
|
13
|
+
end
|
14
|
+
|
15
|
+
def process
|
16
|
+
context.to_hash
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def generate_context
|
22
|
+
director = ::Riddler::ContextDirector.new params: params,
|
23
|
+
headers: headers
|
24
|
+
director.context
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,19 +1,30 @@
|
|
1
1
|
module Riddler
|
2
2
|
module UseCases
|
3
|
-
|
4
3
|
class PreviewStep
|
5
|
-
attr_reader :definition, :
|
4
|
+
attr_reader :definition, :params, :headers, :step
|
6
5
|
|
7
|
-
def initialize definition,
|
6
|
+
def initialize definition, params: {}, headers: {}
|
8
7
|
@definition = definition
|
9
|
-
@
|
8
|
+
@params = params
|
9
|
+
@headers = headers
|
10
10
|
@step = ::Riddler::Step.for definition, context
|
11
11
|
end
|
12
12
|
|
13
|
+
def context
|
14
|
+
@context ||= generate_context
|
15
|
+
end
|
16
|
+
|
13
17
|
def process
|
14
18
|
step.to_hash
|
15
19
|
end
|
16
|
-
end
|
17
20
|
|
21
|
+
private
|
22
|
+
|
23
|
+
def generate_context
|
24
|
+
director = ::Riddler::ContextDirector.new params: params,
|
25
|
+
headers: headers
|
26
|
+
director.context
|
27
|
+
end
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class ShowContentDefinition
|
4
|
+
attr_reader :content_definition_repo,
|
5
|
+
:content_definition_id, :context_director,
|
6
|
+
:content_definition, :step
|
7
|
+
|
8
|
+
def initialize content_definition_repo:, content_definition_id:, context_director:
|
9
|
+
@content_definition_repo = content_definition_repo
|
10
|
+
@content_definition_id = content_definition_id
|
11
|
+
@context_director = context_director
|
12
|
+
|
13
|
+
@content_definition = lookup_content_definition
|
14
|
+
@step = ::Riddler::Step.for content_definition.definition, context
|
15
|
+
end
|
16
|
+
|
17
|
+
def context
|
18
|
+
context_director.context
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
step.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def excluded?
|
26
|
+
!step.include?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def lookup_content_definition
|
32
|
+
content_definition_repo.find_by id: content_definition_id
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_context
|
36
|
+
director = ::Riddler::ContextDirector.new params: params,
|
37
|
+
headers: headers
|
38
|
+
director.context
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class ShowSlug
|
4
|
+
attr_reader :content_definition_repo, :slug_repo, :interaction_repo,
|
5
|
+
:interaction_class, :slug_name, :context_director, :slug, :interaction
|
6
|
+
|
7
|
+
def initialize content_definition_repo:, slug_repo:, interaction_repo:,
|
8
|
+
interaction_class:, slug_name:, params: {}, headers: {}
|
9
|
+
|
10
|
+
@content_definition_repo = content_definition_repo
|
11
|
+
@slug_repo = slug_repo
|
12
|
+
@interaction_repo = interaction_repo
|
13
|
+
@interaction_class = interaction_class
|
14
|
+
@context_director = ::Riddler::ContextDirector.new params: params, headers: headers
|
15
|
+
@slug_name = slug_name
|
16
|
+
@slug = lookup_slug
|
17
|
+
end
|
18
|
+
|
19
|
+
def exists?
|
20
|
+
!slug.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def paused?
|
24
|
+
return true if slug.nil?
|
25
|
+
slug.status == "PAUSED"
|
26
|
+
end
|
27
|
+
|
28
|
+
def dismissed?
|
29
|
+
return false unless slug_defines_identity?
|
30
|
+
find_interaction
|
31
|
+
return false if @interaction.nil?
|
32
|
+
@interaction.status == "DISMISSED"
|
33
|
+
end
|
34
|
+
|
35
|
+
def completed?
|
36
|
+
return false unless slug_defines_identity?
|
37
|
+
find_interaction
|
38
|
+
return false if @interaction.nil?
|
39
|
+
@interaction.status == "COMPLETED"
|
40
|
+
end
|
41
|
+
|
42
|
+
def targeted?
|
43
|
+
slug_targeted?
|
44
|
+
end
|
45
|
+
|
46
|
+
def excluded?
|
47
|
+
definition_use_case.excluded?
|
48
|
+
end
|
49
|
+
|
50
|
+
def process
|
51
|
+
find_interaction || create_interaction
|
52
|
+
context.assign "interaction", interaction.to_hash
|
53
|
+
|
54
|
+
content_hash = definition_use_case.process.merge \
|
55
|
+
interaction_id: interaction.id,
|
56
|
+
dismiss_url: "/interactions/#{interaction.id}/dismiss"
|
57
|
+
|
58
|
+
interaction.content_type = content_hash[:content_type]
|
59
|
+
interaction.content_id = content_hash[:id]
|
60
|
+
|
61
|
+
interaction_repo.update interaction
|
62
|
+
|
63
|
+
content_hash
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def find_interaction
|
69
|
+
return nil unless request_is_unique?
|
70
|
+
|
71
|
+
@interaction ||= interaction_repo.last_by slug_id: slug.id,
|
72
|
+
identity: identity
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_interaction
|
76
|
+
@interaction = interaction_class.new \
|
77
|
+
slug_name: slug.name,
|
78
|
+
slug_id: slug.id,
|
79
|
+
status: "ACTIVE",
|
80
|
+
content_definition_id: slug.content_definition_id,
|
81
|
+
identifiers: context.ids
|
82
|
+
|
83
|
+
@interaction.identity = identity if request_is_unique?
|
84
|
+
|
85
|
+
interaction_repo.create interaction
|
86
|
+
end
|
87
|
+
|
88
|
+
def definition_use_case
|
89
|
+
@definition_use_case ||= ShowContentDefinition.new \
|
90
|
+
content_definition_repo: content_definition_repo,
|
91
|
+
content_definition_id: slug.content_definition_id,
|
92
|
+
context_director: context_director
|
93
|
+
end
|
94
|
+
|
95
|
+
def context
|
96
|
+
context_director.context
|
97
|
+
end
|
98
|
+
|
99
|
+
# Only has IDs extracted - context builders have not been processed
|
100
|
+
def simple_context
|
101
|
+
context_director.simple_context
|
102
|
+
end
|
103
|
+
|
104
|
+
def identity
|
105
|
+
@identity ||= simple_context.render slug.interaction_identity
|
106
|
+
end
|
107
|
+
|
108
|
+
def blank_interaction_identity
|
109
|
+
@blank_interaction_identity ||= ::Riddler::Context.new.render slug.interaction_identity
|
110
|
+
end
|
111
|
+
|
112
|
+
def request_is_unique?
|
113
|
+
slug_defines_identity? && !interaction_identity_is_blank?
|
114
|
+
end
|
115
|
+
|
116
|
+
def interaction_identity_is_blank?
|
117
|
+
identity == blank_interaction_identity
|
118
|
+
end
|
119
|
+
|
120
|
+
def slug_defines_identity?
|
121
|
+
!(slug.interaction_identity.nil? || slug.interaction_identity == "")
|
122
|
+
end
|
123
|
+
|
124
|
+
def lookup_slug
|
125
|
+
slug_repo.find_by name: slug_name
|
126
|
+
end
|
127
|
+
|
128
|
+
def slug_targeted?
|
129
|
+
return true unless slug_has_target_predicate?
|
130
|
+
Predicator.evaluate slug.target_predicate, context.to_liquid
|
131
|
+
end
|
132
|
+
|
133
|
+
def slug_has_target_predicate?
|
134
|
+
slug.target_predicate.to_s.strip != ""
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|