CloudSesame 0.7.5 → 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +14 -12
- data/Guardfile +2 -2
- data/cloud_sesame.gemspec +2 -2
- data/coverage/.last_run.json +1 -1
- data/coverage/.resultset.json +1363 -248
- data/lib/cloud_sesame/domain/base.rb +1 -1
- data/lib/cloud_sesame/query/ast/abstract/multi_expression_operator.rb +3 -4
- data/lib/cloud_sesame/query/ast/abstract/value.rb +10 -2
- data/lib/cloud_sesame/query/ast/date_value.rb +1 -4
- data/lib/cloud_sesame/query/ast/literal.rb +20 -12
- data/lib/cloud_sesame/query/ast/{field_array.rb → multi_expression_operator_children.rb} +8 -2
- data/lib/cloud_sesame/query/ast/not.rb +1 -7
- data/lib/cloud_sesame/query/ast/range_value.rb +43 -28
- data/lib/cloud_sesame/query/ast/root.rb +2 -4
- data/lib/cloud_sesame/query/ast/value.rb +1 -9
- data/lib/cloud_sesame/query/builder.rb +9 -11
- data/lib/cloud_sesame/query/domain/block.rb +1 -3
- data/lib/cloud_sesame/query/domain/chaining_block.rb +3 -4
- data/lib/cloud_sesame/query/domain/literal.rb +1 -3
- data/lib/cloud_sesame/query/dsl/bind_caller.rb +13 -6
- data/lib/cloud_sesame/query/dsl/{field_array_methods.rb → literal_chaining_methods.rb} +1 -6
- data/lib/cloud_sesame/query/dsl/{field_accessors.rb → literal_methods.rb} +1 -1
- data/lib/cloud_sesame/query/node/fuzziness.rb +1 -1
- data/lib/cloud_sesame.rb +4 -3
- data/spec/cloud_sesame/query/ast/abstract/multi_expression_operator_spec.rb +5 -1
- data/spec/cloud_sesame/query/ast/field_array_spec.rb +57 -0
- data/spec/cloud_sesame/query/ast/literal_spec.rb +136 -0
- data/spec/cloud_sesame/query/ast/not_spec.rb +30 -0
- data/spec/cloud_sesame/query/ast/phrase_spec.rb +20 -0
- data/spec/cloud_sesame/query/ast/range_value_spec.rb +245 -30
- data/spec/cloud_sesame/query/ast/string_value_spec.rb +39 -0
- data/spec/cloud_sesame/query/ast/term_spec.rb +20 -0
- data/spec/cloud_sesame/query/ast/value_spec.rb +109 -0
- data/spec/cloud_sesame/query/builder_spec.rb +4 -4
- data/spec/cloud_sesame/query/domain/block_spec.rb +3 -3
- data/spec/cloud_sesame/query/dsl/{field_array_methods_spec.rb → literal_chaining_methods_spec.rb} +1 -1
- data/spec/cloud_sesame/query/dsl/{field_accessors_spec.rb → literal_methods_spec.rb} +3 -3
- data/spec/cloud_sesame/query/node/query_spec.rb +22 -0
- data/spec/profiling_spec.rb +155 -155
- metadata +23 -9
@@ -9,14 +9,14 @@ module CloudSesame
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def applied(included = true)
|
12
|
-
children.map { |child| child.applied
|
12
|
+
children.map { |child| child.applied included }
|
13
13
|
end
|
14
14
|
|
15
15
|
def children
|
16
16
|
@children ||= build_children
|
17
17
|
end
|
18
18
|
|
19
|
-
def compile
|
19
|
+
def compile(_ = nil)
|
20
20
|
if !children.empty? && (compiled = children.compile) && !compiled.empty?
|
21
21
|
"(#{ symbol }#{ boost } #{ compiled })"
|
22
22
|
end
|
@@ -25,8 +25,7 @@ module CloudSesame
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def build_children
|
28
|
-
(
|
29
|
-
children
|
28
|
+
MultiExpressionOperatorChildren.build(self)
|
30
29
|
end
|
31
30
|
|
32
31
|
end
|
@@ -6,6 +6,8 @@ module CloudSesame
|
|
6
6
|
module Abstract
|
7
7
|
class Value
|
8
8
|
|
9
|
+
# CONSTANTS
|
10
|
+
# =====================================
|
9
11
|
RANGE_FORMAT = Regexp.new(/\A(\[|{)(.*),(.*)(\}|\])\z/).freeze
|
10
12
|
DIGIT_FORMAT = Regexp.new(/\A\d+(.\d+)?\z/).freeze
|
11
13
|
DATETIME_FORMAT = Regexp.new(/\d+{4}-\d+{2}-\d+{2}T\d+{2}:\d+{2}:\d+{2}/).freeze
|
@@ -13,12 +15,15 @@ module CloudSesame
|
|
13
15
|
|
14
16
|
attr_reader :value, :changed, :compiled
|
15
17
|
|
18
|
+
# CLASS METHODS
|
19
|
+
# =====================================
|
20
|
+
|
16
21
|
def self.range?(value)
|
17
22
|
value.kind_of?(Range)
|
18
23
|
end
|
19
24
|
|
20
25
|
def self.string_range?(value)
|
21
|
-
RANGE_FORMAT =~ strip(value)
|
26
|
+
value.is_a?(String) && !!(RANGE_FORMAT =~ strip(value))
|
22
27
|
end
|
23
28
|
|
24
29
|
def self.numeric?(value)
|
@@ -26,7 +31,7 @@ module CloudSesame
|
|
26
31
|
end
|
27
32
|
|
28
33
|
def self.string_numeric?(value)
|
29
|
-
DIGIT_FORMAT =~ value
|
34
|
+
value.is_a?(String) && !!(DIGIT_FORMAT =~ value)
|
30
35
|
end
|
31
36
|
|
32
37
|
def self.datetime?(value)
|
@@ -41,6 +46,9 @@ module CloudSesame
|
|
41
46
|
value.is_a?(String) && DATE_FORMAT =~ value
|
42
47
|
end
|
43
48
|
|
49
|
+
# INSTANCE METHODS
|
50
|
+
# =====================================
|
51
|
+
|
44
52
|
def initialize(value, type = nil)
|
45
53
|
self.value = value
|
46
54
|
@type = type
|
@@ -8,10 +8,7 @@ module CloudSesame
|
|
8
8
|
DATE_FORMAT = '%F'.freeze
|
9
9
|
|
10
10
|
def self.parse(value)
|
11
|
-
if value.kind_of?(RangeValue)
|
12
|
-
value.type = self
|
13
|
-
return value
|
14
|
-
end
|
11
|
+
return value.parse self if value.kind_of?(RangeValue)
|
15
12
|
|
16
13
|
range?(value) || string_range?(value) ? RangeValue.new(value, self) :
|
17
14
|
string_datetime?(value) ? new(parse_datetime(value)) :
|
@@ -6,45 +6,53 @@ module CloudSesame
|
|
6
6
|
SINGLE_QUATE = Regexp.new(/\'/).freeze
|
7
7
|
ESCAPE_QUATE = "\\'".freeze
|
8
8
|
|
9
|
-
attr_reader :value
|
9
|
+
attr_reader :field, :value, :options
|
10
10
|
|
11
11
|
def initialize(field, value, options = {})
|
12
12
|
@field = field
|
13
|
-
@options = options
|
14
|
-
@value =
|
13
|
+
@options = options || default_options
|
14
|
+
@value = parse_value(value) if value
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
@
|
19
|
-
@options.merge! options
|
17
|
+
def actual_field_name
|
18
|
+
@options[:as] || @field
|
20
19
|
end
|
21
20
|
|
22
21
|
def applied(included)
|
23
22
|
{ field: @field, value: @value, included: included } if @value
|
24
23
|
end
|
25
24
|
|
26
|
-
def as_field
|
27
|
-
@options[:as] || @field
|
28
|
-
end
|
29
|
-
|
30
25
|
def compile(detailed = false)
|
31
26
|
(detailed ? detailed_format : standard_format) if @value
|
32
27
|
end
|
33
28
|
|
29
|
+
def is_for(field, options)
|
30
|
+
@field = field
|
31
|
+
@options.merge! options if options
|
32
|
+
end
|
33
|
+
|
34
34
|
private
|
35
35
|
|
36
36
|
def standard_format
|
37
|
-
"#{
|
37
|
+
"#{ actual_field_name }:#{ @value.compile }"
|
38
38
|
end
|
39
39
|
|
40
40
|
def detailed_format
|
41
|
-
"field=#{ escape
|
41
|
+
"field=#{ escape actual_field_name.to_s } #{ @value.compile }"
|
42
42
|
end
|
43
43
|
|
44
44
|
def escape(data)
|
45
45
|
"'#{ data.gsub(SINGLE_QUATE) { ESCAPE_QUATE } }'"
|
46
46
|
end
|
47
47
|
|
48
|
+
def parse_value(value)
|
49
|
+
LazyObject.new { (@options[:type] || Value).parse(value) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_options
|
53
|
+
{ type: Value }
|
54
|
+
end
|
55
|
+
|
48
56
|
end
|
49
57
|
end
|
50
58
|
end
|
@@ -1,12 +1,18 @@
|
|
1
1
|
module CloudSesame
|
2
2
|
module Query
|
3
3
|
module AST
|
4
|
-
class
|
5
|
-
include DSL::
|
4
|
+
class MultiExpressionOperatorChildren < Array
|
5
|
+
include DSL::LiteralChainingMethods
|
6
6
|
|
7
7
|
attr_accessor :_scope, :_return
|
8
8
|
attr_reader :field
|
9
9
|
|
10
|
+
def self.build(scope, children = nil)
|
11
|
+
array = new(children || [])
|
12
|
+
array._scope = scope
|
13
|
+
array
|
14
|
+
end
|
15
|
+
|
10
16
|
def field=(field)
|
11
17
|
parents.clear
|
12
18
|
@field = field
|
@@ -4,14 +4,8 @@ module CloudSesame
|
|
4
4
|
class Not < Abstract::SingleExpressionOperator
|
5
5
|
SYMBOL = :not
|
6
6
|
|
7
|
-
def compile
|
8
|
-
if child && (compiled = child.compile) && !compiled.empty?
|
9
|
-
"(#{ symbol }#{ boost } #{ compiled })"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
7
|
def applied(included = true)
|
14
|
-
child.applied
|
8
|
+
child.applied !included
|
15
9
|
end
|
16
10
|
|
17
11
|
end
|
@@ -4,35 +4,54 @@ module CloudSesame
|
|
4
4
|
class RangeValue < Abstract::Value
|
5
5
|
|
6
6
|
def initialize(value = nil, type = nil)
|
7
|
-
self.value =
|
8
|
-
|
9
|
-
|
7
|
+
self.value = RangeValue.range?(value) ? build_from_range(value) :
|
8
|
+
RangeValue.string_range?(value) ? build_from_string(value) :
|
9
|
+
initialize_value
|
10
|
+
self.parse type
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(type)
|
14
|
+
if type && type.respond_to?(:parse)
|
15
|
+
@changed = true
|
16
|
+
value[1] = type.parse(self.begin) unless self.begin.to_s.empty?
|
17
|
+
value[2] = type.parse(self.end) unless self.end.to_s.empty?
|
18
|
+
end
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
def begin
|
23
|
+
value[1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def end
|
27
|
+
value[2]
|
28
|
+
end
|
10
29
|
|
11
|
-
|
30
|
+
def lower_bound
|
31
|
+
value[0]
|
12
32
|
end
|
13
33
|
|
14
|
-
def
|
15
|
-
|
16
|
-
type
|
34
|
+
def upper_bound
|
35
|
+
value[3]
|
17
36
|
end
|
18
37
|
|
19
38
|
def gt(value)
|
20
|
-
|
39
|
+
set_begin(value) if value
|
21
40
|
return self
|
22
41
|
end
|
23
42
|
|
24
43
|
def gte(value)
|
25
|
-
|
44
|
+
set_begin(value, '[') if value
|
26
45
|
return self
|
27
46
|
end
|
28
47
|
|
29
48
|
def lt(value)
|
30
|
-
|
49
|
+
set_end(value) if value
|
31
50
|
return self
|
32
51
|
end
|
33
52
|
|
34
53
|
def lte(value)
|
35
|
-
|
54
|
+
set_end(value, ']') if value
|
36
55
|
return self
|
37
56
|
end
|
38
57
|
|
@@ -41,7 +60,7 @@ module CloudSesame
|
|
41
60
|
end
|
42
61
|
|
43
62
|
def ==(object)
|
44
|
-
value == RangeValue.new(object, Value).value
|
63
|
+
value == (object.is_a?(RangeValue) ? object : RangeValue.new(object, Value)).value
|
45
64
|
end
|
46
65
|
|
47
66
|
private
|
@@ -50,30 +69,26 @@ module CloudSesame
|
|
50
69
|
super "#{ value[0] }#{ value[1] },#{ value[2] }#{ value[3] }"
|
51
70
|
end
|
52
71
|
|
53
|
-
def
|
54
|
-
|
55
|
-
self.value[
|
56
|
-
end
|
57
|
-
|
58
|
-
def update_uppoer_value(value, included = '}')
|
59
|
-
self.value[2] = value
|
60
|
-
self.value[3] = included
|
72
|
+
def set_begin(value, included = '{')
|
73
|
+
@changed = true
|
74
|
+
self.value[0, 2] = [included, value]
|
61
75
|
end
|
62
76
|
|
63
|
-
def
|
64
|
-
|
77
|
+
def set_end(value, included = '}')
|
78
|
+
@changed = true
|
79
|
+
self.value[2, 2] = [value, included]
|
65
80
|
end
|
66
81
|
|
67
|
-
def
|
68
|
-
|
82
|
+
def build_from_range(range)
|
83
|
+
initialize_value('[', range.begin, range.end, range.exclude_end? ? '}' : ']')
|
69
84
|
end
|
70
85
|
|
71
|
-
def
|
72
|
-
|
86
|
+
def build_from_string(string)
|
87
|
+
initialize_value(*RANGE_FORMAT.match(strip(string)).captures)
|
73
88
|
end
|
74
89
|
|
75
|
-
def
|
76
|
-
[
|
90
|
+
def initialize_value(lb = '[', bv = nil, ev = nil, up = ']')
|
91
|
+
[lb, bv, ev, up]
|
77
92
|
end
|
78
93
|
|
79
94
|
end
|
@@ -13,21 +13,13 @@ module CloudSesame
|
|
13
13
|
(klass =TYPES[symbol]) ? klass : self
|
14
14
|
end
|
15
15
|
|
16
|
-
# if the value is already a range value object
|
17
|
-
# set the type to Value and return the value
|
18
|
-
# else determine the type of value and create it
|
19
16
|
def self.parse(value)
|
20
|
-
if value.kind_of?(RangeValue)
|
21
|
-
value.type = self
|
22
|
-
return value
|
23
|
-
end
|
24
|
-
|
17
|
+
return value.parse self if value.kind_of?(RangeValue)
|
25
18
|
(
|
26
19
|
range_value?(value) ? RangeValue :
|
27
20
|
numeric_value?(value) ? NumericValue :
|
28
21
|
datetime?(value) ? DateValue : StringValue
|
29
22
|
).new(value, self)
|
30
|
-
|
31
23
|
end
|
32
24
|
|
33
25
|
def self.range_value?(value)
|
@@ -15,16 +15,16 @@ module CloudSesame
|
|
15
15
|
# ClassSpecific construct class callback
|
16
16
|
#
|
17
17
|
# after construct searchable specific builder,
|
18
|
-
# construct searchable specific DSL::
|
18
|
+
# construct searchable specific DSL::LiteralMethods,
|
19
19
|
# and Domain::Block and include the new field accessors
|
20
20
|
# in both builder and domain block
|
21
21
|
# ===================================================
|
22
22
|
after_construct do |searchable|
|
23
|
-
@
|
24
|
-
@block_domain = Domain::Block.construct_class(searchable, callback_args: [
|
23
|
+
@literal_methods = DSL::LiteralMethods.construct_module(searchable)
|
24
|
+
@block_domain = Domain::Block.construct_class(searchable, callback_args: [literal_methods])
|
25
25
|
@request = Node::Request.construct_class(searchable)
|
26
26
|
|
27
|
-
include
|
27
|
+
include literal_methods
|
28
28
|
end
|
29
29
|
|
30
30
|
# Domain::Block getter
|
@@ -37,9 +37,9 @@ module CloudSesame
|
|
37
37
|
@request ||= Node::Request
|
38
38
|
end
|
39
39
|
|
40
|
-
# DSL::
|
41
|
-
def self.
|
42
|
-
@
|
40
|
+
# DSL::LiteralMethods getter
|
41
|
+
def self.literal_methods
|
42
|
+
@literal_methods ||= DSL::LiteralMethods
|
43
43
|
end
|
44
44
|
|
45
45
|
# ===================================================
|
@@ -62,10 +62,8 @@ module CloudSesame
|
|
62
62
|
private
|
63
63
|
|
64
64
|
def _block_domain(block)
|
65
|
-
|
66
|
-
|
67
|
-
self.class.block_domain.new caller, _context
|
68
|
-
end
|
65
|
+
caller = block ? block.binding.eval("self") : nil
|
66
|
+
self.class.block_domain.new caller, _context
|
69
67
|
end
|
70
68
|
|
71
69
|
def _scope
|
@@ -15,11 +15,9 @@ module CloudSesame
|
|
15
15
|
attr_reader :_caller, :_context, :_scopes
|
16
16
|
|
17
17
|
def initialize(_caller, _context)
|
18
|
-
|
18
|
+
self._caller = _caller
|
19
19
|
@_context = _context
|
20
20
|
@_scopes = []
|
21
|
-
|
22
|
-
_bind_caller_instance_variables
|
23
21
|
end
|
24
22
|
|
25
23
|
def _eval(node, _scope, _return = _scope, &block)
|
@@ -28,10 +28,9 @@ module CloudSesame
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def _block_domain(block)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
)
|
31
|
+
caller = block.binding.eval("self")
|
32
|
+
@_block_domain._caller = caller
|
33
|
+
@_block_domain
|
35
34
|
end
|
36
35
|
|
37
36
|
def missing_block
|
@@ -3,11 +3,16 @@ module CloudSesame
|
|
3
3
|
module DSL
|
4
4
|
module BindCaller
|
5
5
|
|
6
|
+
def _caller=(caller)
|
7
|
+
__bind_caller_instance_variables__(caller) if caller
|
8
|
+
@_caller = caller
|
9
|
+
end
|
10
|
+
|
6
11
|
private
|
7
12
|
|
8
|
-
def
|
9
|
-
|
10
|
-
value =
|
13
|
+
def __bind_caller_instance_variables__(caller)
|
14
|
+
caller.instance_variables.each do |name|
|
15
|
+
value = caller.instance_variable_get name
|
11
16
|
instance_variable_set name, value
|
12
17
|
end
|
13
18
|
end
|
@@ -15,9 +20,11 @@ module CloudSesame
|
|
15
20
|
# ACCESS CALLER'S METHODS
|
16
21
|
# =========================================
|
17
22
|
def method_missing(name, *args, &block)
|
18
|
-
_caller.
|
19
|
-
|
20
|
-
|
23
|
+
if _caller && _caller.respond_to?(name, *args, &block)
|
24
|
+
_caller.send(name, *args, &block)
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
21
28
|
end
|
22
29
|
|
23
30
|
end
|
@@ -1,12 +1,7 @@
|
|
1
|
-
# =========================================================
|
2
|
-
# field array methods enables field array to chain
|
3
|
-
# operators after calling the field expression
|
4
|
-
# accessor
|
5
|
-
# =========================================================
|
6
1
|
module CloudSesame
|
7
2
|
module Query
|
8
3
|
module DSL
|
9
|
-
module
|
4
|
+
module LiteralChainingMethods
|
10
5
|
|
11
6
|
# NOT
|
12
7
|
# =======================================
|
data/lib/cloud_sesame.rb
CHANGED
@@ -25,10 +25,11 @@ require 'cloud_sesame/config/credential'
|
|
25
25
|
require 'cloud_sesame/query/dsl/applied_filter_query'
|
26
26
|
require 'cloud_sesame/query/dsl/bind_caller'
|
27
27
|
require 'cloud_sesame/query/dsl/block_styled_operators'
|
28
|
-
require 'cloud_sesame/query/dsl/
|
28
|
+
require 'cloud_sesame/query/dsl/literal_methods'
|
29
|
+
require 'cloud_sesame/query/dsl/literal_chaining_methods'
|
29
30
|
require 'cloud_sesame/query/dsl/scope_accessors'
|
30
31
|
require 'cloud_sesame/query/dsl/operators'
|
31
|
-
|
32
|
+
|
32
33
|
require 'cloud_sesame/query/dsl/inspect_method'
|
33
34
|
require 'cloud_sesame/query/dsl/page_methods'
|
34
35
|
require 'cloud_sesame/query/dsl/query_methods'
|
@@ -49,7 +50,7 @@ require 'cloud_sesame/query/ast/abstract/operator'
|
|
49
50
|
require 'cloud_sesame/query/ast/abstract/multi_expression_operator'
|
50
51
|
require 'cloud_sesame/query/ast/abstract/single_expression_operator'
|
51
52
|
require 'cloud_sesame/query/ast/abstract/value'
|
52
|
-
require 'cloud_sesame/query/ast/
|
53
|
+
require 'cloud_sesame/query/ast/multi_expression_operator_children'
|
53
54
|
require 'cloud_sesame/query/ast/and'
|
54
55
|
require 'cloud_sesame/query/ast/or'
|
55
56
|
require 'cloud_sesame/query/ast/not'
|
@@ -19,11 +19,15 @@ module CloudSesame
|
|
19
19
|
expect(subject.children).to be_empty
|
20
20
|
end
|
21
21
|
it 'should be an Field Array' do
|
22
|
-
expect(subject.children).to be_a AST::
|
22
|
+
expect(subject.children).to be_a AST::MultiExpressionOperatorChildren
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
describe '#compile' do
|
27
|
+
it 'should accept one optional param' do
|
28
|
+
expect{ subject.compile(true) }.to_not raise_error
|
29
|
+
end
|
30
|
+
|
27
31
|
context 'when theres children' do
|
28
32
|
let(:child) { OpenStruct.new(compile: "compiled") }
|
29
33
|
before { subject.children << child }
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module CloudSesame
|
2
|
+
module Query
|
3
|
+
module AST
|
4
|
+
describe MultiExpressionOperatorChildren do
|
5
|
+
|
6
|
+
describe '.build' do
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
context 'when children is given' do
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#field=' do
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#parents' do
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#_context' do
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#compile' do
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# class FieldArray < Array
|
32
|
+
# include DSL::FieldArrayMethods
|
33
|
+
|
34
|
+
# attr_accessor :_scope, :_return
|
35
|
+
# attr_reader :field
|
36
|
+
|
37
|
+
# def field=(field)
|
38
|
+
# parents.clear
|
39
|
+
# @field = field
|
40
|
+
# end
|
41
|
+
|
42
|
+
# def parents
|
43
|
+
# @parents ||= []
|
44
|
+
# end
|
45
|
+
|
46
|
+
# def _context
|
47
|
+
# _scope && _scope.context
|
48
|
+
# end
|
49
|
+
|
50
|
+
# def compile
|
51
|
+
# map(&:compile).join(' ')
|
52
|
+
# end
|
53
|
+
|
54
|
+
# end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|