levels 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rbenv-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +12 -0
- data/Guardfile +14 -0
- data/LICENSE +22 -0
- data/README.md +315 -0
- data/Rakefile +28 -0
- data/bin/levels +130 -0
- data/examples/01_base.rb +6 -0
- data/examples/01_merge_to_json.sh +27 -0
- data/examples/01_prod.json +8 -0
- data/examples/02_base.rb +4 -0
- data/examples/02_merge_with_file.sh +20 -0
- data/examples/02_value +1 -0
- data/levels.gemspec +20 -0
- data/lib/levels.rb +77 -0
- data/lib/levels/audit.rb +24 -0
- data/lib/levels/audit/group_observer.rb +26 -0
- data/lib/levels/audit/nested_group_observer.rb +37 -0
- data/lib/levels/audit/root_observer.rb +63 -0
- data/lib/levels/audit/value.rb +64 -0
- data/lib/levels/audit/value_observer.rb +46 -0
- data/lib/levels/audit/values.rb +66 -0
- data/lib/levels/configuration.rb +98 -0
- data/lib/levels/configured_group.rb +62 -0
- data/lib/levels/event_handler.rb +127 -0
- data/lib/levels/group.rb +61 -0
- data/lib/levels/input/json.rb +17 -0
- data/lib/levels/input/ruby.rb +120 -0
- data/lib/levels/input/system.rb +63 -0
- data/lib/levels/input/yaml.rb +17 -0
- data/lib/levels/key.rb +28 -0
- data/lib/levels/key_values.rb +54 -0
- data/lib/levels/lazy_evaluator.rb +54 -0
- data/lib/levels/level.rb +80 -0
- data/lib/levels/method_missing.rb +14 -0
- data/lib/levels/output/json.rb +33 -0
- data/lib/levels/output/system.rb +29 -0
- data/lib/levels/output/yaml.rb +19 -0
- data/lib/levels/runtime.rb +30 -0
- data/lib/levels/setup.rb +132 -0
- data/lib/levels/system/constants.rb +8 -0
- data/lib/levels/system/key_formatter.rb +15 -0
- data/lib/levels/system/key_generator.rb +50 -0
- data/lib/levels/system/key_parser.rb +67 -0
- data/lib/levels/version.rb +3 -0
- data/test/acceptance/audit_test.rb +105 -0
- data/test/acceptance/event_handler_test.rb +43 -0
- data/test/acceptance/read_json_test.rb +35 -0
- data/test/acceptance/read_ruby_test.rb +117 -0
- data/test/acceptance/read_system_test.rb +121 -0
- data/test/acceptance/read_yaml_test.rb +38 -0
- data/test/acceptance/setup_test.rb +115 -0
- data/test/acceptance/write_json_test.rb +39 -0
- data/test/acceptance/write_system_test.rb +68 -0
- data/test/acceptance/write_yaml_test.rb +33 -0
- data/test/bin/merge_test.rb +194 -0
- data/test/bin/options_test.rb +41 -0
- data/test/helper.rb +12 -0
- data/test/support/acceptance_spec.rb +58 -0
- data/test/support/base_spec.rb +14 -0
- data/test/support/bin_spec.rb +65 -0
- data/test/support/tempfile_helper.rb +35 -0
- data/test/unit/audit/group_observer_test.rb +24 -0
- data/test/unit/audit/nested_group_observer_test.rb +28 -0
- data/test/unit/audit/root_observer_test.rb +54 -0
- data/test/unit/audit/value_observer_test.rb +63 -0
- data/test/unit/audit/value_test.rb +41 -0
- data/test/unit/audit/values_test.rb +86 -0
- data/test/unit/configuration_test.rb +72 -0
- data/test/unit/configured_group_test.rb +75 -0
- data/test/unit/group_test.rb +105 -0
- data/test/unit/input/json_test.rb +32 -0
- data/test/unit/input/ruby_test.rb +140 -0
- data/test/unit/input/system_test.rb +59 -0
- data/test/unit/input/yaml_test.rb +33 -0
- data/test/unit/key_test.rb +45 -0
- data/test/unit/key_values_test.rb +106 -0
- data/test/unit/lazy_evaluator_test.rb +38 -0
- data/test/unit/level_test.rb +89 -0
- data/test/unit/levels_test.rb +23 -0
- data/test/unit/output/json_test.rb +55 -0
- data/test/unit/output/system_test.rb +32 -0
- data/test/unit/output/yaml_test.rb +38 -0
- data/test/unit/runtime_test.rb +40 -0
- data/test/unit/system/key_formatter_test.rb +43 -0
- data/test/unit/system/key_generator_test.rb +21 -0
- data/test/unit/system/key_parser_test.rb +207 -0
- metadata +215 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
module Levels
|
2
|
+
# A Configuration is the merging of one or more levels. This is the top level
|
3
|
+
# object that you will interact with most.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# # In this example we'll represent a Level as a Hash.
|
8
|
+
# level1 = { group1: { key1: 1 } }
|
9
|
+
# level2 = { group1: { key1: 2, key2: 2 }, group2: { x: 9 } }
|
10
|
+
#
|
11
|
+
# # The configuration exposes each group that exists in a Level.
|
12
|
+
# config = Levels::Configuration.new([level1, level2])
|
13
|
+
#
|
14
|
+
# # A Group may be accessed via hash syntax.
|
15
|
+
# config[:group1] # => { key1: 2, key2: 2 }
|
16
|
+
# # Or method syntax.
|
17
|
+
# config.group1 # => { key1: 2, key2: 2 }
|
18
|
+
#
|
19
|
+
# # You can check if a Group exists.
|
20
|
+
# config.defined?(:group1) # => true
|
21
|
+
# config.group1? # => true
|
22
|
+
#
|
23
|
+
class Configuration
|
24
|
+
include Levels::MethodMissing
|
25
|
+
|
26
|
+
# Internal: Initialze a new configuration.
|
27
|
+
#
|
28
|
+
# levels - Array of Levels::Level.
|
29
|
+
#
|
30
|
+
def initialize(levels, event_handler = nil)
|
31
|
+
@levels = levels
|
32
|
+
@event_handler = event_handler || NullEventHandler.new
|
33
|
+
@root_observer = Levels::Audit.start(LazyEvaluator.new(self))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Set the event handler. The event handler is notified whenever a
|
37
|
+
# key is read; allowing you to track exactly what is and isn't used at
|
38
|
+
# runtime.
|
39
|
+
#
|
40
|
+
# event_handler - Levels::EventHandler.
|
41
|
+
#
|
42
|
+
# Returns nothing.
|
43
|
+
def event_handler=(event_handler)
|
44
|
+
@event_handler = event_handler
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Retrieve a group. The resulting group is the union of the named
|
48
|
+
# group from each Level that defines that group.
|
49
|
+
#
|
50
|
+
# group_key - Symbol name of the group.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# # In this example we'll represent a Level as a Hash.
|
55
|
+
# level1 = { group: { key1: 1 } }
|
56
|
+
# level2 = { group: { key1: 2, key2: 2 } }
|
57
|
+
#
|
58
|
+
# config = Levels::Configuration.new([level1, level2])
|
59
|
+
# config[:group] # => { key1: 2, key2: 2 }
|
60
|
+
#
|
61
|
+
# Returns a Levels::ConfiguredGroup.
|
62
|
+
# Raises Levels::UnknownGroup if the group is not defined.
|
63
|
+
def [](group_key)
|
64
|
+
raise UnknownGroup unless self.defined?(group_key)
|
65
|
+
group_observer = @root_observer.observe_group(@event_handler)
|
66
|
+
Levels::ConfiguredGroup.new(@levels, group_key, group_observer)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Determine if a group is defined. A group is defined if it exists
|
70
|
+
# in any Level.
|
71
|
+
#
|
72
|
+
# group_key - Symbol name of the group.
|
73
|
+
#
|
74
|
+
# Returns a Boolean.
|
75
|
+
def defined?(group_key)
|
76
|
+
@levels.any? { |level| level.defined?(group_key) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
"<Levels::Configuration #{@levels.map { |l| l._level_name }.join(', ')}>"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an Enumerator which yields [gruop_name, Group#to_enum].
|
84
|
+
def to_enum
|
85
|
+
Enumerator.new do |y|
|
86
|
+
group_keys = Set.new
|
87
|
+
@levels.each do |level|
|
88
|
+
level.to_enum.each do |name, group|
|
89
|
+
group_keys << name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
group_keys.each do |name|
|
93
|
+
y << [name, self[name].to_enum]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Levels
|
2
|
+
# A ConfiguredGroup is the union of one or more Groups. When a key is read,
|
3
|
+
# the value returned is the value from the last Level that defines that key.
|
4
|
+
class ConfiguredGroup
|
5
|
+
include Levels::MethodMissing
|
6
|
+
|
7
|
+
# Internal: Initialize a configured group.
|
8
|
+
#
|
9
|
+
def initialize(levels, group_key, group_observer)
|
10
|
+
@levels = levels
|
11
|
+
@group_key = group_key
|
12
|
+
@group_observer = group_observer
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Retrieve a value.
|
16
|
+
#
|
17
|
+
# value_key - Symbol name of the value.
|
18
|
+
#
|
19
|
+
# Returns the value stored at that key.
|
20
|
+
# Raises Levels::UnknownKey if the key is not defined.
|
21
|
+
def [](value_key)
|
22
|
+
raise UnknownKey unless self.defined?(value_key)
|
23
|
+
values = @group_observer.observe_values(@levels, @group_key, value_key)
|
24
|
+
values.final_value
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Determine if a key is defined.
|
28
|
+
#
|
29
|
+
# value_key - Symbol name of the key.
|
30
|
+
#
|
31
|
+
# Returns a Boolean.
|
32
|
+
def defined?(value_key)
|
33
|
+
groups.any? { |group| group.defined?(value_key) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"<Levels::ConfiguredGroup #{@group_key}>"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an Enumerator which yields [key, value].
|
41
|
+
def to_enum
|
42
|
+
Enumerator.new do |y|
|
43
|
+
value_keys = Set.new
|
44
|
+
groups.each do |group|
|
45
|
+
group.to_enum.each do |key, value|
|
46
|
+
value_keys << key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
value_keys.each do |key|
|
50
|
+
y << [key, self[key]]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def groups
|
58
|
+
levels = @levels.find_all { |level| level.defined?(@group_key) }
|
59
|
+
levels.map { |level| level[@group_key] }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Levels
|
2
|
+
# This is the interface for capturing what happens when a value is read.
|
3
|
+
# Capturing events exposes you to the audit trail system implemented in
|
4
|
+
# Levels::Audit.
|
5
|
+
#
|
6
|
+
# class MyEventHandler
|
7
|
+
#
|
8
|
+
# def on_values(values)
|
9
|
+
# # This method is called any time a value is accessed.
|
10
|
+
# # The argument `values` is a Levels::Audit::Values representing the
|
11
|
+
# # set of all possible values.
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def on_nested_values(values)
|
15
|
+
# # Similar to `on_values`, but called when the values were found
|
16
|
+
# # during the evaluation of another value.
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
module EventHandler
|
21
|
+
|
22
|
+
def on_values(values)
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_nested_values(values)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# A null implementation.
|
30
|
+
class NullEventHandler
|
31
|
+
include EventHandler
|
32
|
+
end
|
33
|
+
|
34
|
+
module Colorizer
|
35
|
+
|
36
|
+
RESET = "\033[0m"
|
37
|
+
FOREGROUND = {
|
38
|
+
black: "\033[30m",
|
39
|
+
red: "\033[31m",
|
40
|
+
green: "\033[32m",
|
41
|
+
brown: "\033[33m",
|
42
|
+
blue: "\033[34m",
|
43
|
+
magenta: "\033[35m",
|
44
|
+
cyan: "\033[36m",
|
45
|
+
white: "\033[37m"
|
46
|
+
}
|
47
|
+
|
48
|
+
def foreground_color(name, str)
|
49
|
+
code = FOREGROUND[name] or raise ArgumentError, "Unknown color #{name.inspect}"
|
50
|
+
"#{code}#{str}#{RESET}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class CliEventHandler
|
55
|
+
include Colorizer
|
56
|
+
|
57
|
+
def initialize(stream, color = false)
|
58
|
+
@stream = stream
|
59
|
+
@color = color
|
60
|
+
@indent = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_values(values)
|
64
|
+
write :white, "> #{values.group_key}.#{values.value_key}"
|
65
|
+
values.each do |value|
|
66
|
+
value.notify(self)
|
67
|
+
if value.final?
|
68
|
+
write :green, " + #{value.inspect} from #{value.level_name}"
|
69
|
+
else
|
70
|
+
write :red, " - #{value.inspect} from #{value.level_name}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_nested_values(values)
|
76
|
+
indent do
|
77
|
+
on_values(values)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def indent
|
84
|
+
begin
|
85
|
+
@indent += 1
|
86
|
+
yield
|
87
|
+
ensure
|
88
|
+
@indent -= 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def write(color, str)
|
93
|
+
prefix = " " * @indent
|
94
|
+
str = foreground_color(color, str) if @color
|
95
|
+
@stream.puts prefix + str
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# A temporary implementation that will be moved back to config to
|
100
|
+
# restore its original behavior.
|
101
|
+
class ConfigCoreEventHandler
|
102
|
+
|
103
|
+
def initialize(log)
|
104
|
+
@log = log
|
105
|
+
@base_color = :magenta
|
106
|
+
@alt_color = :cyan
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_read_from_merged_group(group_name, key, levels)
|
110
|
+
final_level_name, final_value = levels.last
|
111
|
+
|
112
|
+
if levels.size == 1
|
113
|
+
@log << @log.colorize("Read #{group_name}.#{key} => #{final_value.inspect} from #{final_level_name}", @base_color)
|
114
|
+
else
|
115
|
+
skipped_levels = levels[0..-2]
|
116
|
+
|
117
|
+
@log << @log.colorize("Read #{group_name}.#{key}", @base_color)
|
118
|
+
@log.indent do
|
119
|
+
skipped_levels.each do |level_name, value|
|
120
|
+
@log << @log.colorize("Skip #{value.inspect} from #{level_name}", @alt_color)
|
121
|
+
end
|
122
|
+
@log << @log.colorize("Use #{final_value.inspect} from #{final_level_name}", @base_color)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/levels/group.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Levels
|
2
|
+
# A group is a set of key/value pairs containing your data.
|
3
|
+
#
|
4
|
+
# Examples
|
5
|
+
#
|
6
|
+
# group = Levels::Group.new(name: "Joe", age: 33)
|
7
|
+
#
|
8
|
+
# group.name # => "Joe"
|
9
|
+
# group.age # => 33
|
10
|
+
#
|
11
|
+
class Group
|
12
|
+
include Levels::MethodMissing
|
13
|
+
|
14
|
+
# Internal: Initialize a new group.
|
15
|
+
#
|
16
|
+
# data - Hash of key/values for the group.
|
17
|
+
# value_transformer - Proc that takes (key, value) and returns value.
|
18
|
+
#
|
19
|
+
def initialize(data = {}, value_transformer = nil)
|
20
|
+
@values = Levels::KeyValues.new(data)
|
21
|
+
@value_transformer = value_transformer || -> key, value { value }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Get the value for a key.
|
25
|
+
#
|
26
|
+
# Returns the value.
|
27
|
+
# Raises Levels::UnknownKey if the key is not defined.
|
28
|
+
def [](value_key)
|
29
|
+
if @values.key?(value_key)
|
30
|
+
key, value = @values.pair(value_key)
|
31
|
+
@value_transformer.call(key.to_sym, value)
|
32
|
+
else
|
33
|
+
raise UnknownKey, "#{value_key.inspect} is not defined in #{self}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Determine if a key is defined.
|
38
|
+
#
|
39
|
+
# Returns a Boolean.
|
40
|
+
def defined?(key)
|
41
|
+
@values.key?(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
"<Levels::Group>"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns an Enumerator which yields [key, value].
|
49
|
+
def to_enum
|
50
|
+
Enumerator.new do |y|
|
51
|
+
@values.each do |key, value|
|
52
|
+
y << [key.to_sym, self[key]]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def eql_hash?(hash)
|
58
|
+
@values == Levels::KeyValues.new(hash)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Levels
|
2
|
+
module Input
|
3
|
+
# This input
|
4
|
+
class JSON
|
5
|
+
|
6
|
+
def initialize(json_string)
|
7
|
+
@json = ::JSON.parse(json_string)
|
8
|
+
end
|
9
|
+
|
10
|
+
def read(level)
|
11
|
+
@json.each do |group_name, group|
|
12
|
+
level.set_group(group_name, group)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Levels
|
2
|
+
module Input
|
3
|
+
# This input provides a DSL for writing levels in ruby.
|
4
|
+
class Ruby
|
5
|
+
|
6
|
+
def initialize(ruby_string, file = "Code in Memory", line = 1)
|
7
|
+
@ruby_string = ruby_string
|
8
|
+
@file = file
|
9
|
+
@line = line
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(level)
|
13
|
+
dsl = DSL.new(level)
|
14
|
+
dsl.instance_eval(@ruby_string, @file, @line)
|
15
|
+
dsl.close_current_group
|
16
|
+
end
|
17
|
+
|
18
|
+
# The Ruby syntax DSL. This syntax aims to be very readable and writable.
|
19
|
+
# A stateful syntax and no trailing commas allows values to be easily
|
20
|
+
# moved around without excess text editing.
|
21
|
+
#
|
22
|
+
# Examples
|
23
|
+
#
|
24
|
+
# group :webserver
|
25
|
+
# set hostname: "localhost"
|
26
|
+
# group :task_queue
|
27
|
+
# set workers: 1
|
28
|
+
# set queues: -> { ["high", "low", webserver.hostname] }
|
29
|
+
#
|
30
|
+
class DSL
|
31
|
+
include Levels::Runtime
|
32
|
+
|
33
|
+
def initialize(level)
|
34
|
+
@level = level
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Start a group.
|
38
|
+
#
|
39
|
+
# name - Symbol or String name of the group.
|
40
|
+
#
|
41
|
+
# Returns nothing.
|
42
|
+
# Raises a RuntimeError if the group has already been defined.
|
43
|
+
def group(name)
|
44
|
+
close_current_group
|
45
|
+
if @level.defined?(name)
|
46
|
+
raise RuntimeError, "Group has already been created: #{name.inspect}"
|
47
|
+
end
|
48
|
+
@group = name
|
49
|
+
@hash = {}
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Set a key/value pair in the current group.
|
54
|
+
#
|
55
|
+
# Arguments
|
56
|
+
#
|
57
|
+
# hash - Hash of key/value pairs. The keys may be a Symbol or String.
|
58
|
+
#
|
59
|
+
# key - Symbol or String key.
|
60
|
+
# value - Any value.
|
61
|
+
#
|
62
|
+
# Examples
|
63
|
+
#
|
64
|
+
# # Ruby 1.9 Hash.
|
65
|
+
# set key: "value"
|
66
|
+
#
|
67
|
+
# # Ruby 1.9 Hash.
|
68
|
+
# set :key => "value"
|
69
|
+
#
|
70
|
+
# # Key can be a String.
|
71
|
+
# set "key" => "value"
|
72
|
+
#
|
73
|
+
# # Multiple key/values.
|
74
|
+
# set :key => "value", :more => "values"
|
75
|
+
#
|
76
|
+
# # Key, value.
|
77
|
+
# set :key, "value"
|
78
|
+
#
|
79
|
+
# Returns nothing.
|
80
|
+
# Raises SyntaxError if no current group is defined.
|
81
|
+
# Raises ArgumentError if other forms of arguments are given
|
82
|
+
# Raises RuntimeError if a key has already been set.
|
83
|
+
def set(*args)
|
84
|
+
raise SyntaxError, "No group is defined" if @hash.nil?
|
85
|
+
case
|
86
|
+
when args.size == 1 && Hash === args.first
|
87
|
+
hash = args.first
|
88
|
+
when args.size == 2 && !(args.any? { |a| Hash === a })
|
89
|
+
hash = { args.first => args.last }
|
90
|
+
else
|
91
|
+
raise ArgumentError, "Set must be given a Hash or two arguments. Got #{args.inspect}"
|
92
|
+
end
|
93
|
+
key_values = Levels::KeyValues.new(@hash)
|
94
|
+
hash.keys.each do |key|
|
95
|
+
if key_values.key?(key)
|
96
|
+
raise RuntimeError, "Key has already been set: #{key.inspect}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
@hash.update(hash)
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def close_current_group
|
104
|
+
@level.set_group(@group, @hash) if @group
|
105
|
+
@group = nil
|
106
|
+
@hash = nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
"<Levels>"
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"<Levels>"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|