server_settings 0.1.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 +14 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +77 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +202 -0
- data/Rakefile +2 -0
- data/example/sample.rb +53 -0
- data/lib/capistrano/server_settings.rb +8 -0
- data/lib/server_settings.rb +113 -0
- data/lib/server_settings/capistrano.rb +23 -0
- data/lib/server_settings/database.rb +29 -0
- data/lib/server_settings/database_config.rb +46 -0
- data/lib/server_settings/host.rb +16 -0
- data/lib/server_settings/host_collection.rb +31 -0
- data/lib/server_settings/railtie.rb +10 -0
- data/lib/server_settings/role.rb +42 -0
- data/lib/server_settings/role_db.rb +59 -0
- data/lib/server_settings/tasks.rb +9 -0
- data/lib/server_settings/tasks/db.rake +98 -0
- data/lib/server_settings/version.rb +3 -0
- data/server_settings.gemspec +25 -0
- data/spec/lib/server_settings/database_config_spec.rb +97 -0
- data/spec/lib/servers_config_spec.rb +300 -0
- data/spec/spec_helper.rb +91 -0
- data/spec/test-yaml/role1.yml +3 -0
- data/spec/test-yaml/role2.yml +3 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 83912df73bcdbd70f0ce5eac116555996c082697
|
4
|
+
data.tar.gz: e97bd645bf4c53a24337a0765ffcba5f47fdf40c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2b1f4bcd7f69204e9a62b910797c2aa2b0a58cf75ae9f9580633ffc726bdf28d5969a50d8be07926817e05cfba88c8dc2914d0525b8f2cafea12d774153b0af6
|
7
|
+
data.tar.gz: 09993ce27d7bc5c793a18c2d34a183dc997e64f19724655e34a75268401c8d8143d00d54a3a480f19054a20556785568c7b3577cd53bb2b1adcf64db6a3a4bb0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## [v0.0.7](https://github.com/monsterstrike/server_settings/tree/v0.0.7) (2015-07-28)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/monsterstrike/server_settings/compare/v0.0.6...v0.0.7)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Support capistrano3 [\#18](https://github.com/monsterstrike/server_settings/pull/18) ([hirocaster](https://github.com/hirocaster))
|
10
|
+
|
11
|
+
## [v0.0.6](https://github.com/monsterstrike/server_settings/tree/v0.0.6) (2015-06-18)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/monsterstrike/server_settings/compare/v0.0.5...v0.0.6)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- Add ServerSettings::Database and db iterator [\#16](https://github.com/monsterstrike/server_settings/pull/16) ([osamu](https://github.com/osamu))
|
18
|
+
|
19
|
+
- Raise error duplicate role [\#12](https://github.com/monsterstrike/server_settings/pull/12) ([osamu](https://github.com/osamu))
|
20
|
+
|
21
|
+
## [v0.0.5](https://github.com/monsterstrike/server_settings/tree/v0.0.5) (2015-06-03)
|
22
|
+
|
23
|
+
[Full Changelog](https://github.com/monsterstrike/server_settings/compare/v0.0.4...v0.0.5)
|
24
|
+
|
25
|
+
**Merged pull requests:**
|
26
|
+
|
27
|
+
- Add hosts exception [\#15](https://github.com/monsterstrike/server_settings/pull/15) ([osamu](https://github.com/osamu))
|
28
|
+
|
29
|
+
## [v0.0.4](https://github.com/monsterstrike/server_settings/tree/v0.0.4) (2015-05-25)
|
30
|
+
|
31
|
+
[Full Changelog](https://github.com/monsterstrike/server_settings/compare/v0.0.3...v0.0.4)
|
32
|
+
|
33
|
+
**Closed issues:**
|
34
|
+
|
35
|
+
- Rendering YAML using ERB [\#9](https://github.com/monsterstrike/server_settings/issues/9)
|
36
|
+
|
37
|
+
**Merged pull requests:**
|
38
|
+
|
39
|
+
- Add yaml erb rendering [\#10](https://github.com/monsterstrike/server_settings/pull/10) ([osamu](https://github.com/osamu))
|
40
|
+
|
41
|
+
## [v0.0.3](https://github.com/monsterstrike/server_settings/tree/v0.0.3) (2015-05-19)
|
42
|
+
|
43
|
+
[Full Changelog](https://github.com/monsterstrike/server_settings/compare/v0.0.2...v0.0.3)
|
44
|
+
|
45
|
+
**Merged pull requests:**
|
46
|
+
|
47
|
+
- Fix cap role definition for ServerSettings::HostCollection [\#8](https://github.com/monsterstrike/server_settings/pull/8) ([osamu](https://github.com/osamu))
|
48
|
+
|
49
|
+
- Capistrano server loader task can load from directory pattern [\#7](https://github.com/monsterstrike/server_settings/pull/7) ([osamu](https://github.com/osamu))
|
50
|
+
|
51
|
+
- Fix spec filename [\#6](https://github.com/monsterstrike/server_settings/pull/6) ([osamu](https://github.com/osamu))
|
52
|
+
|
53
|
+
## [v0.0.2](https://github.com/monsterstrike/server_settings/tree/v0.0.2) (2015-05-14)
|
54
|
+
|
55
|
+
[Full Changelog](https://github.com/monsterstrike/server_settings/compare/v0.0.1...v0.0.2)
|
56
|
+
|
57
|
+
**Closed issues:**
|
58
|
+
|
59
|
+
- compliant with naming rules for rubygems [\#4](https://github.com/monsterstrike/server_settings/issues/4)
|
60
|
+
|
61
|
+
**Merged pull requests:**
|
62
|
+
|
63
|
+
- Rename gem package server\_settings [\#5](https://github.com/monsterstrike/server_settings/pull/5) ([osamu](https://github.com/osamu))
|
64
|
+
|
65
|
+
- \[Fix\] Rename gemspec [\#3](https://github.com/monsterstrike/server_settings/pull/3) ([osamu](https://github.com/osamu))
|
66
|
+
|
67
|
+
- Split files for each module [\#2](https://github.com/monsterstrike/server_settings/pull/2) ([osamu](https://github.com/osamu))
|
68
|
+
|
69
|
+
## [v0.0.1](https://github.com/monsterstrike/server_settings/tree/v0.0.1) (2015-05-14)
|
70
|
+
|
71
|
+
**Merged pull requests:**
|
72
|
+
|
73
|
+
- Add availability azone [\#1](https://github.com/monsterstrike/server_settings/pull/1) ([osamu](https://github.com/osamu))
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Osamu MATSUMOTO
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# ServerSettings
|
2
|
+
|
3
|
+
ServerSettings is useful configuration scheme for any where.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'server_settings'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install server_settings
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Prepare
|
25
|
+
First of all, you must define servers and role in YAML
|
26
|
+
|
27
|
+
```yaml
|
28
|
+
(rolename):
|
29
|
+
(option_param): (option_value)
|
30
|
+
(...)
|
31
|
+
hosts:
|
32
|
+
- host1
|
33
|
+
- host2
|
34
|
+
```
|
35
|
+
Then, You can load yaml files.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'server_settings'
|
39
|
+
|
40
|
+
ServerSettings.load_config("path/to/yaml")
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Load from directory
|
44
|
+
If you have many roles and servers, you can split yaml file and put
|
45
|
+
into directory.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
|
49
|
+
ServerSettings.load_config_dir("path/to/yamls-dir/*.yml")
|
50
|
+
```
|
51
|
+
|
52
|
+
### Render YAML with ERB
|
53
|
+
When your server configuration has dynamic parameter, you can use ERB
|
54
|
+
syntax in yaml.
|
55
|
+
|
56
|
+
`load_config_dir()` will rendering ERB template every time.
|
57
|
+
Or `load_from_yaml_erb()` also rendering ERB for yaml content
|
58
|
+
|
59
|
+
### For Dalli
|
60
|
+
|
61
|
+
```yaml
|
62
|
+
memcached_servers:
|
63
|
+
port: 11211
|
64
|
+
hosts:
|
65
|
+
- 192.168.100.1
|
66
|
+
- 192.168.100.2
|
67
|
+
```
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
ServerSettings.load_config("config/production/server-config.yaml")
|
71
|
+
|
72
|
+
ActiveSupport::Cache::DalliStore.new ServerSettings.memcached_servers.with_format("%host:%port"), options
|
73
|
+
|
74
|
+
```
|
75
|
+
|
76
|
+
### For Resque host
|
77
|
+
```yaml
|
78
|
+
redis_endpoint:
|
79
|
+
port: 6379
|
80
|
+
hosts:
|
81
|
+
- 192.168.100.1
|
82
|
+
```
|
83
|
+
When hosts have single record, host accessor return string value
|
84
|
+
instend of Array.
|
85
|
+
```
|
86
|
+
Resque.redis = ServerSettings.redis_endpoint.with_format("%host:%port")
|
87
|
+
|
88
|
+
```
|
89
|
+
### For DB
|
90
|
+
|
91
|
+
```yaml
|
92
|
+
databases:
|
93
|
+
:adapter: mysql2
|
94
|
+
:encoding: utf8
|
95
|
+
:reconnect: true
|
96
|
+
:database: app
|
97
|
+
:pool: 10
|
98
|
+
:username: db_user1
|
99
|
+
:password: db_pass1
|
100
|
+
:master: 192.168.30.86
|
101
|
+
:backup: 192.168.30.85
|
102
|
+
db1:
|
103
|
+
:database: app_db1
|
104
|
+
:master: 192.168.30.86
|
105
|
+
:backup: 192.168.30.85
|
106
|
+
:slaves: [ 192.168.30.87, 192.168.30.88 ]
|
107
|
+
group1:
|
108
|
+
db2:
|
109
|
+
:master: 192.168.30.86
|
110
|
+
:backup: 192.168.30.85
|
111
|
+
```
|
112
|
+
#### Access DB configuration
|
113
|
+
```ruby
|
114
|
+
ServerSettings.load_config("config/production/server-config.yaml")
|
115
|
+
|
116
|
+
ServerSettings.databases.find("db1").config(:master)
|
117
|
+
# => {:adapter=>"mysql2", :encoding=>"utf8", :reconnect=>true, :database=>"app_db1", :pool=>10, :username=>"db_user1", :password=>"db_pass1", :host=>"192.168.30.86"}
|
118
|
+
ServerSettings.databases.find("db1").config(:backup)
|
119
|
+
# => {:adapter=>"mysql2", :encoding=>"utf8", :reconnect=>true, :database=>"app_db1", :pool=>10, :username=>"db_user1", :password=>"db_pass1", :host=>"192.168.30.85"}
|
120
|
+
ServerSettings.databases.find("db1").config(:slaves)
|
121
|
+
# =>[ {:adapter=>"mysql2", :encoding=>"utf8", :reconnect=>true, :database=>"app_db1", :pool=>10, :username=>"db_user1", :password=>"db_pass1", :host=>"192.168.30.87"},
|
122
|
+
# {:adapter=>"mysql2", :encoding=>"utf8", :reconnect=>true, :database=>"app_db1", :pool=>10, :username=>"db_user1", :password=>"db_pass1", :host=>"192.168.30.88"}]
|
123
|
+
```
|
124
|
+
#### Iterator for all db config
|
125
|
+
```ruby
|
126
|
+
ServerSettings.load_config("config/production/server-config.yaml")
|
127
|
+
|
128
|
+
ServerSettings.databases.each do |db|
|
129
|
+
db.master # => 192.168.30.86
|
130
|
+
db.backup # => 192.168.30.85
|
131
|
+
db.slaves # => [ 192.168.30.87, 192.168.30.88 ]
|
132
|
+
db.has_slave? # => true
|
133
|
+
db.settings # => {:adapter=>"mysql2", :encoding=>"utf8", :reconnect=>true, :database=>"app_db1", :pool=>10, :username=>"db_user1", :password=>"db_pass1"}
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
#### Tasks for mysql
|
138
|
+
|
139
|
+
If you use Rails, ServerSettings define following tasks.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
rake server_settings:db:create:execute # Confirm and execute CREATE DATABASE for each new database
|
143
|
+
rake server_settings:db:create:status # Show status of new databases not created yet
|
144
|
+
rake server_settings:db:drop # Confirm and execute Drop DATABASE for all database
|
145
|
+
```
|
146
|
+
|
147
|
+
If not use Rails, you writes in `Rakefile` as follows, define tasks.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
require 'server_settings/tasks'
|
151
|
+
```
|
152
|
+
|
153
|
+
### For Rails
|
154
|
+
|
155
|
+
When use Rails, place yaml file in the following pattern.
|
156
|
+
|
157
|
+
ServerSettings autoload this yaml file and define roles.
|
158
|
+
|
159
|
+
`#{Rails.root}/config/servers/#{Rails.env}/*.yml`
|
160
|
+
|
161
|
+
### For Capistrano2
|
162
|
+
```ruby
|
163
|
+
require 'server_settings/capistrano'
|
164
|
+
|
165
|
+
load_servers("config/production/*.yaml")
|
166
|
+
|
167
|
+
```
|
168
|
+
|
169
|
+
### For Capistrano3
|
170
|
+
|
171
|
+
in Capfile
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
require "capistrano/server_settings"
|
175
|
+
```
|
176
|
+
|
177
|
+
in deploy.rb
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
load_servers("config/production/*.yaml")
|
181
|
+
```
|
182
|
+
|
183
|
+
## For other application configuration
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
ServerSettings.load_config("config/production/server-config.yaml")
|
187
|
+
ServerSettings.each_role do |role, config|
|
188
|
+
puts "#{role}, #{config.hosts}"
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
puts ServerSettings.memcached_servers.to_a
|
194
|
+
|
195
|
+
```
|
196
|
+
## Contributing
|
197
|
+
|
198
|
+
1. Fork it ( https://github.com/monsterstrike/server_settings/fork )
|
199
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
200
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
201
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
202
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/example/sample.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'server_settings'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
yaml =<<EOF
|
5
|
+
redis:
|
6
|
+
port: 6379
|
7
|
+
hosts:
|
8
|
+
- 192.168.100.1
|
9
|
+
|
10
|
+
app:
|
11
|
+
protocol: http
|
12
|
+
user: hogehoge
|
13
|
+
port: 8080
|
14
|
+
hosts:
|
15
|
+
- 192.168.100.1
|
16
|
+
- 192.168.100.2
|
17
|
+
- 192.168.100.3:8000
|
18
|
+
|
19
|
+
database:
|
20
|
+
:adapter: mysql2
|
21
|
+
:encoding: utf8
|
22
|
+
:reconnect: true
|
23
|
+
:database: dbname-master
|
24
|
+
:pool: 1
|
25
|
+
:username: user
|
26
|
+
:password: pass
|
27
|
+
:host: 192.168.100.1
|
28
|
+
master:
|
29
|
+
:host: 192.168.100.2
|
30
|
+
user:
|
31
|
+
:database: dbname-user
|
32
|
+
:host: 192.168.100.3
|
33
|
+
EOF
|
34
|
+
|
35
|
+
# Load Configuration
|
36
|
+
ServerSettings.load_from_yaml(yaml)
|
37
|
+
|
38
|
+
# Define Host format
|
39
|
+
#ServerSettings.host_format[:default] = "%host:%port"
|
40
|
+
#ServerSettings.host_format[:redis] = "redis://%host:%port"
|
41
|
+
|
42
|
+
# Role and Host accessor
|
43
|
+
p ServerSettings.app.hosts
|
44
|
+
p ServerSettings.app.hosts.with_format("%protocol://%user@%host:%port")
|
45
|
+
|
46
|
+
# Role Iterator
|
47
|
+
ServerSettings.each_role do |role, role_config|
|
48
|
+
puts "#{role}"
|
49
|
+
puts role_config
|
50
|
+
end
|
51
|
+
|
52
|
+
# Database Configuration
|
53
|
+
p ServerSettings.database.hosts
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'yaml'
|
3
|
+
require 'erb'
|
4
|
+
require "server_settings/version"
|
5
|
+
require "server_settings/host"
|
6
|
+
require "server_settings/host_collection"
|
7
|
+
require "server_settings/role"
|
8
|
+
require "server_settings/role_db"
|
9
|
+
require "server_settings/database"
|
10
|
+
require "server_settings/database_config"
|
11
|
+
require "server_settings/railtie" if defined? Rails
|
12
|
+
|
13
|
+
class ServerSettings
|
14
|
+
attr_accessor :roles
|
15
|
+
|
16
|
+
## Exceptions
|
17
|
+
class DuplicateRole < StandardError; end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@roles = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def << (role)
|
24
|
+
raise DuplicateRole, "`#{role.name}' already defined" if @roles.has_key?(role.name)
|
25
|
+
@roles[role.name] = role
|
26
|
+
end
|
27
|
+
|
28
|
+
def each
|
29
|
+
@roles.each do |name, role|
|
30
|
+
yield(name, role)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_key?(role)
|
35
|
+
@roles.has_key?(role)
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(name, *args, &block)
|
39
|
+
key = name.to_s
|
40
|
+
return nil unless has_key? key
|
41
|
+
@roles[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
class << self
|
45
|
+
|
46
|
+
def load_config(file)
|
47
|
+
@loaded_files ||= {}
|
48
|
+
load_from_yaml_erb(IO.read(file))
|
49
|
+
@loaded_files[file] = File.mtime(file)
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_config_dir(pattern)
|
53
|
+
Dir.glob(pattern) do |file|
|
54
|
+
load_config(file)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_from_yaml(yaml)
|
59
|
+
config = YAML.load(yaml)
|
60
|
+
config.each do |role, config|
|
61
|
+
instance << role_klass(config).new(role, config)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_from_yaml_erb(yaml, erb_binding: binding)
|
66
|
+
yaml = ERB.new(yaml).result(erb_binding)
|
67
|
+
load_from_yaml(yaml)
|
68
|
+
end
|
69
|
+
|
70
|
+
def reload
|
71
|
+
@loaded_files.each do |file, updated_at|
|
72
|
+
if File.mtime(file) > updated_at
|
73
|
+
load_config(file)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def roles
|
79
|
+
instance.roles
|
80
|
+
end
|
81
|
+
|
82
|
+
def each_role
|
83
|
+
roles.each do |role, config|
|
84
|
+
yield(role, config.hosts)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def role_klass(config)
|
89
|
+
if config.has_key?("hosts")
|
90
|
+
Role
|
91
|
+
else
|
92
|
+
RoleDB
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def destroy
|
97
|
+
@servers_config = nil
|
98
|
+
@loaded_files = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def instance
|
104
|
+
return @servers_config if @servers_config
|
105
|
+
@servers_config = self.new
|
106
|
+
return @servers_config
|
107
|
+
end
|
108
|
+
|
109
|
+
def method_missing(name, *args, &block)
|
110
|
+
instance.send(name, *args, &block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|