coral_core 0.2.26 → 0.2.30
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.
- data/Gemfile +7 -2
- data/Gemfile.lock +84 -15
- data/VERSION +1 -1
- data/coral_core.gemspec +51 -16
- data/lib/{coral_core/command.rb → coral/command/shell.rb} +12 -116
- data/lib/coral/machine/fog.rb +215 -0
- data/lib/coral/network/default.rb +26 -0
- data/lib/coral/node/rackspace.rb +23 -0
- data/lib/coral_core.rb +249 -134
- data/lib/coral_core/config.rb +233 -275
- data/lib/coral_core/config/collection.rb +57 -0
- data/lib/coral_core/config/options.rb +70 -0
- data/lib/coral_core/config/project.rb +225 -0
- data/lib/coral_core/core.rb +19 -173
- data/lib/coral_core/event/puppet_event.rb +98 -0
- data/lib/coral_core/mixin/config_collection.rb +52 -0
- data/lib/coral_core/mixin/config_ops.rb +51 -0
- data/lib/coral_core/mixin/config_options.rb +38 -0
- data/lib/coral_core/mixin/lookup.rb +211 -0
- data/lib/coral_core/mixin/macro/object_interface.rb +292 -0
- data/lib/coral_core/mixin/macro/plugin_interface.rb +277 -0
- data/lib/coral_core/mixin/settings.rb +46 -0
- data/lib/coral_core/mixin/sub_config.rb +208 -0
- data/lib/coral_core/mod/hash.rb +29 -0
- data/lib/{hiera_backend.rb → coral_core/mod/hiera_backend.rb} +0 -0
- data/lib/coral_core/plugin.rb +261 -0
- data/lib/coral_core/plugin/command.rb +95 -0
- data/lib/coral_core/plugin/machine.rb +152 -0
- data/lib/coral_core/plugin/network.rb +24 -0
- data/lib/coral_core/plugin/node.rb +184 -0
- data/lib/coral_core/plugin_base.rb +147 -0
- data/lib/coral_core/repository.rb +471 -82
- data/lib/coral_core/util/cli.rb +293 -0
- data/lib/coral_core/util/data.rb +178 -8
- data/lib/coral_core/util/disk.rb +13 -0
- data/lib/coral_core/util/git.rb +35 -10
- data/lib/coral_core/{interface.rb → util/interface.rb} +31 -21
- data/lib/coral_core/util/process.rb +43 -0
- data/locales/en.yml +8 -0
- data/spec/coral_core/interface_spec.rb +45 -45
- metadata +109 -34
- data/.gitmodules +0 -12
- data/lib/coral_core/memory.rb +0 -226
- data/lib/coral_core/util/git/base.rb +0 -65
- data/lib/coral_core/util/git/lib.rb +0 -89
- data/lib/coral_core/util/git/remote.rb +0 -12
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
# Should be included via include
|
|
3
|
+
#
|
|
4
|
+
# include Mixin::Settings
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
module Coral
|
|
8
|
+
module Mixin
|
|
9
|
+
module Settings
|
|
10
|
+
|
|
11
|
+
def settings(name)
|
|
12
|
+
return get_hash([ :settings, name ])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#---
|
|
16
|
+
|
|
17
|
+
def set_settings(name, settings = {})
|
|
18
|
+
return set([ :settings, name ], settings)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#---
|
|
22
|
+
|
|
23
|
+
def delete_settings(name)
|
|
24
|
+
return delete([ :settings, name ])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#---
|
|
28
|
+
|
|
29
|
+
def setting(name, key, default = '', format = false)
|
|
30
|
+
return get([ :settings, name, key ], default, format)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#---
|
|
34
|
+
|
|
35
|
+
def set_setting(name, key, value = '')
|
|
36
|
+
return set([ :settings, name, key ], value)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#---
|
|
40
|
+
|
|
41
|
+
def delete_setting(name, key)
|
|
42
|
+
return delete([ :settings, name, key ])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
|
|
2
|
+
# Should be included via include
|
|
3
|
+
#
|
|
4
|
+
# include Mixin::SubConfig
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
module Coral
|
|
8
|
+
module Mixin
|
|
9
|
+
module SubConfig
|
|
10
|
+
|
|
11
|
+
#-----------------------------------------------------------------------------
|
|
12
|
+
# Initialization
|
|
13
|
+
|
|
14
|
+
def init_subconfig(reset = false)
|
|
15
|
+
return if @subconfig_initialized && ! reset
|
|
16
|
+
|
|
17
|
+
unless @config
|
|
18
|
+
@config = Config.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@subconfig_initialized = true
|
|
22
|
+
end
|
|
23
|
+
protected :init_subconfig
|
|
24
|
+
|
|
25
|
+
#-----------------------------------------------------------------------------
|
|
26
|
+
# Propety accessors / modifiers
|
|
27
|
+
|
|
28
|
+
def name
|
|
29
|
+
return _get(:name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#---
|
|
33
|
+
|
|
34
|
+
def name=name
|
|
35
|
+
_set(:name, string(name))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#---
|
|
39
|
+
|
|
40
|
+
def config
|
|
41
|
+
return @config
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#---
|
|
45
|
+
|
|
46
|
+
def config=config
|
|
47
|
+
@config = config
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#---
|
|
51
|
+
|
|
52
|
+
def directory
|
|
53
|
+
init_subconfig
|
|
54
|
+
|
|
55
|
+
if config.is_a?(Config::Project)
|
|
56
|
+
return config.project.directory
|
|
57
|
+
else
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#---
|
|
63
|
+
|
|
64
|
+
def directory=directory
|
|
65
|
+
init_subconfig
|
|
66
|
+
|
|
67
|
+
if config.is_a?(Config::Project)
|
|
68
|
+
config.set_location(directory)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#-----------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def _get(keys, default = nil, format = false)
|
|
75
|
+
return fetch(@properties, array(keys).flatten, default, format)
|
|
76
|
+
end
|
|
77
|
+
protected :_get
|
|
78
|
+
|
|
79
|
+
#---
|
|
80
|
+
|
|
81
|
+
def get(keys, default = nil, format = false)
|
|
82
|
+
init_subconfig
|
|
83
|
+
return config.get(keys, default, format)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#---
|
|
87
|
+
|
|
88
|
+
def _init(keys, default = nil)
|
|
89
|
+
return _set(keys, _get(keys, default))
|
|
90
|
+
end
|
|
91
|
+
protected :_init
|
|
92
|
+
|
|
93
|
+
#---
|
|
94
|
+
|
|
95
|
+
def _set(keys, value = '')
|
|
96
|
+
modify(@properties, array(keys).flatten, value)
|
|
97
|
+
return self
|
|
98
|
+
end
|
|
99
|
+
protected :_set
|
|
100
|
+
|
|
101
|
+
#---
|
|
102
|
+
|
|
103
|
+
def set(keys, value = '')
|
|
104
|
+
init_subconfig
|
|
105
|
+
config.set(keys, value)
|
|
106
|
+
return self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#---
|
|
110
|
+
|
|
111
|
+
def _delete(keys, default = nil)
|
|
112
|
+
existing = modify(@properties, array(keys).flatten, nil)
|
|
113
|
+
return existing[:value] if existing[:value]
|
|
114
|
+
return default
|
|
115
|
+
end
|
|
116
|
+
protected :_delete
|
|
117
|
+
|
|
118
|
+
#---
|
|
119
|
+
|
|
120
|
+
def delete(keys, default = nil)
|
|
121
|
+
init_subconfig
|
|
122
|
+
config.delete(keys, default)
|
|
123
|
+
return self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#---
|
|
127
|
+
|
|
128
|
+
def _clear
|
|
129
|
+
@properties = {}
|
|
130
|
+
return self
|
|
131
|
+
end
|
|
132
|
+
protected :_clear
|
|
133
|
+
|
|
134
|
+
#---
|
|
135
|
+
|
|
136
|
+
def clear
|
|
137
|
+
init_subconfig
|
|
138
|
+
config.clear
|
|
139
|
+
return self
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
#-----------------------------------------------------------------------------
|
|
143
|
+
# Import / Export
|
|
144
|
+
|
|
145
|
+
def _import(properties, options = {})
|
|
146
|
+
return import_base(properties, options)
|
|
147
|
+
end
|
|
148
|
+
protected :_import
|
|
149
|
+
|
|
150
|
+
#---
|
|
151
|
+
|
|
152
|
+
def import(properties, options = {})
|
|
153
|
+
init_subconfig
|
|
154
|
+
config.import(properties, options)
|
|
155
|
+
return self
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#---
|
|
159
|
+
|
|
160
|
+
def _defaults(defaults, options = {})
|
|
161
|
+
config = new(options).set(:import_type, :default)
|
|
162
|
+
return import_base(defaults, config)
|
|
163
|
+
end
|
|
164
|
+
protected :_defaults
|
|
165
|
+
|
|
166
|
+
#---
|
|
167
|
+
|
|
168
|
+
def defaults(defaults, options = {})
|
|
169
|
+
init_subconfig
|
|
170
|
+
config.defaults(defaults, options)
|
|
171
|
+
return self
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
#---
|
|
175
|
+
|
|
176
|
+
def _export
|
|
177
|
+
return @properties
|
|
178
|
+
end
|
|
179
|
+
protected :_export
|
|
180
|
+
|
|
181
|
+
#---
|
|
182
|
+
|
|
183
|
+
def export
|
|
184
|
+
init_subconfig
|
|
185
|
+
return config.export
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
#-----------------------------------------------------------------------------
|
|
189
|
+
# Configuration loading saving
|
|
190
|
+
|
|
191
|
+
def load(options = {})
|
|
192
|
+
if config.respond_to?(:load)
|
|
193
|
+
config.load(options)
|
|
194
|
+
end
|
|
195
|
+
return self
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
#---
|
|
199
|
+
|
|
200
|
+
def save(options = {})
|
|
201
|
+
if config.respond_to?(:save)
|
|
202
|
+
config.save(options)
|
|
203
|
+
end
|
|
204
|
+
return self
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
#-------------------------------------------------------------------------------
|
|
3
|
+
# Hash data type alterations
|
|
4
|
+
|
|
5
|
+
class Hash
|
|
6
|
+
def search(search_key, options = {})
|
|
7
|
+
config = Coral::Config.ensure(options)
|
|
8
|
+
value = nil
|
|
9
|
+
|
|
10
|
+
recurse = config.get(:recurse, false)
|
|
11
|
+
recurse_level = config.get(:recurse_level, -1)
|
|
12
|
+
|
|
13
|
+
self.each do |key, data|
|
|
14
|
+
if key == search_key
|
|
15
|
+
value = data
|
|
16
|
+
|
|
17
|
+
elsif data.is_a?(Hash) &&
|
|
18
|
+
recurse && (recurse_level == -1 || recurse_level > 0)
|
|
19
|
+
|
|
20
|
+
recurse_level -= 1 unless recurse_level == -1
|
|
21
|
+
value = value.search(search_key,
|
|
22
|
+
Coral::Config.new(config).set(:recurse_level, recurse_level)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
break unless value.nil?
|
|
26
|
+
end
|
|
27
|
+
return value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
|
|
2
|
+
module Coral
|
|
3
|
+
module Plugin
|
|
4
|
+
|
|
5
|
+
#-----------------------------------------------------------------------------
|
|
6
|
+
# Plugin instances
|
|
7
|
+
|
|
8
|
+
@@load_info = {}
|
|
9
|
+
@@types = {}
|
|
10
|
+
@@plugins = {}
|
|
11
|
+
|
|
12
|
+
#---
|
|
13
|
+
|
|
14
|
+
@@gems = {}
|
|
15
|
+
@@core = nil
|
|
16
|
+
|
|
17
|
+
#---
|
|
18
|
+
|
|
19
|
+
def self.create_instance(type, provider, options = {})
|
|
20
|
+
type = type.to_sym
|
|
21
|
+
provider = provider.to_sym
|
|
22
|
+
|
|
23
|
+
return nil unless @@types.has_key?(type)
|
|
24
|
+
|
|
25
|
+
options = translate_type(type, options)
|
|
26
|
+
provider = options.delete(:provider).to_sym if options.has_key?(:provider)
|
|
27
|
+
info = @@load_info[type][provider] if Util::Data.exists?(@@load_info, [ type, provider ])
|
|
28
|
+
|
|
29
|
+
if info
|
|
30
|
+
options = translate(type, provider, options)
|
|
31
|
+
instance_name = "#{provider}_" + Coral.sha1(options)
|
|
32
|
+
|
|
33
|
+
@@plugins[type] = {} unless @@plugins.has_key?(type)
|
|
34
|
+
|
|
35
|
+
unless instance_name && @@plugins[type].has_key?(instance_name)
|
|
36
|
+
info[:instance_name] = instance_name
|
|
37
|
+
options[:meta] = info
|
|
38
|
+
|
|
39
|
+
plugin = Coral.class_const([ :coral, type, provider ]).new(type, provider, options)
|
|
40
|
+
|
|
41
|
+
@@plugins[type][instance_name] = plugin
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return @@plugins[type][instance_name]
|
|
45
|
+
end
|
|
46
|
+
return nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#---
|
|
50
|
+
|
|
51
|
+
def self.get_instance(type, name)
|
|
52
|
+
if @@plugins.has_key?(type)
|
|
53
|
+
@@plugins[type].each do |instance_name, plugin|
|
|
54
|
+
return plugin if plugin.name.to_s == name.to_s
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
return nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#---
|
|
61
|
+
|
|
62
|
+
def self.remove_instance(plugin)
|
|
63
|
+
if plugin && plugin.is_a?(Plugin::Base) && @@plugins.has_key?(plugin.plugin_type)
|
|
64
|
+
@@plugins[plugin.plugin_type].delete(plugin.plugin_instance_name)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#-----------------------------------------------------------------------------
|
|
69
|
+
# Plugins and resources
|
|
70
|
+
|
|
71
|
+
def self.core
|
|
72
|
+
return @@core
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#---
|
|
76
|
+
|
|
77
|
+
def self.register_gem(spec)
|
|
78
|
+
plugin_path = File.join(spec.full_gem_path, 'lib', 'coral')
|
|
79
|
+
if File.directory?(plugin_path)
|
|
80
|
+
@@gems[spec.name] = {
|
|
81
|
+
:lib_dir => plugin_path,
|
|
82
|
+
:spec => spec
|
|
83
|
+
}
|
|
84
|
+
if spec.name == 'coral_core'
|
|
85
|
+
@@core = spec
|
|
86
|
+
else
|
|
87
|
+
register(plugin_path) # Autoload plugins and related files
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#---
|
|
93
|
+
|
|
94
|
+
def self.gems(reset = false)
|
|
95
|
+
if reset || Util::Data.empty?(@@gems)
|
|
96
|
+
if defined?(Gem)
|
|
97
|
+
if ! defined?(Bundler) && Gem::Specification.respond_to?(:latest_specs)
|
|
98
|
+
Gem::Specification.latest_specs(true).each do |spec|
|
|
99
|
+
register_gem(spec)
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
Gem.loaded_specs.each do |name, spec|
|
|
103
|
+
register_gem(spec)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
return @@gems
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#-----------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def self.define_type(type_info)
|
|
114
|
+
if type_info.is_a?(Hash)
|
|
115
|
+
type_info.each do |type, default_provider|
|
|
116
|
+
@@types[type.to_sym] = default_provider
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
#---
|
|
122
|
+
|
|
123
|
+
def self.types
|
|
124
|
+
return @@types.keys
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
#---
|
|
128
|
+
|
|
129
|
+
def self.type_default(type)
|
|
130
|
+
return @@types[type.to_sym]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#---
|
|
134
|
+
|
|
135
|
+
def self.plugins(type = nil)
|
|
136
|
+
results = {}
|
|
137
|
+
|
|
138
|
+
if type
|
|
139
|
+
results[type] = @@plugins[type] if @@plugins.has_key?(type)
|
|
140
|
+
else
|
|
141
|
+
results = @@plugins
|
|
142
|
+
end
|
|
143
|
+
return results
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
#---
|
|
147
|
+
|
|
148
|
+
def self.add_build_info(type, file)
|
|
149
|
+
type = type.to_sym
|
|
150
|
+
|
|
151
|
+
@@load_info[type] = {} unless @@load_info.has_key?(type)
|
|
152
|
+
|
|
153
|
+
components = file.split(File::SEPARATOR)
|
|
154
|
+
provider = components.pop.sub(/\.rb/, '').to_sym
|
|
155
|
+
directory = components.join(File::SEPARATOR)
|
|
156
|
+
|
|
157
|
+
puts 'Loading ' + type.to_s + ' plugin: ' + provider.to_s
|
|
158
|
+
|
|
159
|
+
unless @@load_info[type].has_key?(provider)
|
|
160
|
+
data = {
|
|
161
|
+
:type => type,
|
|
162
|
+
:provider => provider,
|
|
163
|
+
:directory => directory,
|
|
164
|
+
:file => file
|
|
165
|
+
}
|
|
166
|
+
@@load_info[type][provider] = data
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
#-----------------------------------------------------------------------------
|
|
171
|
+
# Plugin autoloading
|
|
172
|
+
|
|
173
|
+
def self.register_type(base_path, plugin_type)
|
|
174
|
+
base_directory = File.join(base_path, plugin_type.to_s)
|
|
175
|
+
|
|
176
|
+
if File.directory?(base_directory)
|
|
177
|
+
Dir.glob(File.join(base_directory, '*.rb')).each do |file|
|
|
178
|
+
add_build_info(plugin_type, file)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
#---
|
|
184
|
+
|
|
185
|
+
def self.register(base_path)
|
|
186
|
+
if File.directory?(base_path)
|
|
187
|
+
Dir.glob(File.join(base_path, '*.rb')).each do |file|
|
|
188
|
+
require file
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
Dir.entries(base_path).each do |path|
|
|
192
|
+
unless path.match(/^\.\.?$/)
|
|
193
|
+
register_type(base_path, path)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#---
|
|
200
|
+
|
|
201
|
+
def self.autoload
|
|
202
|
+
@@load_info.keys.each do |type|
|
|
203
|
+
@@load_info[type].each do |provider, plugin|
|
|
204
|
+
coral_require(plugin[:directory], provider)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
#---
|
|
210
|
+
|
|
211
|
+
@@initialized = false
|
|
212
|
+
|
|
213
|
+
#---
|
|
214
|
+
|
|
215
|
+
def self.initialize
|
|
216
|
+
unless @@initialized
|
|
217
|
+
# Register core plugins
|
|
218
|
+
register(File.join(File.dirname(__FILE__), '..', 'coral'))
|
|
219
|
+
|
|
220
|
+
# Register external Gem defined plugins
|
|
221
|
+
gems(true)
|
|
222
|
+
|
|
223
|
+
# Autoload the registered plugins
|
|
224
|
+
autoload
|
|
225
|
+
|
|
226
|
+
@@initialized = true
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
#---
|
|
231
|
+
|
|
232
|
+
def self.initialized?
|
|
233
|
+
return @@initialized
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
#-----------------------------------------------------------------------------
|
|
237
|
+
# Utilities
|
|
238
|
+
|
|
239
|
+
def self.translate_type(type, info, method = :translate)
|
|
240
|
+
klass = Coral.class_const([ :coral, :plugin, type ])
|
|
241
|
+
info = klass.send(method, info) if klass.respond_to?(method)
|
|
242
|
+
return info
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
#---
|
|
246
|
+
|
|
247
|
+
def self.translate(type, provider, info, method = :translate)
|
|
248
|
+
klass = Coral.class_const([ :coral, type, provider ])
|
|
249
|
+
info = klass.send(method, info) if klass.respond_to?(method)
|
|
250
|
+
return info
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
#-----------------------------------------------------------------------------
|
|
254
|
+
# Core plugin types
|
|
255
|
+
|
|
256
|
+
define_type :network => :default,
|
|
257
|
+
:node => :rackspace,
|
|
258
|
+
:machine => :fog,
|
|
259
|
+
:command => :shell
|
|
260
|
+
end
|
|
261
|
+
end
|