rabbitt-configurator 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +339 -0
- data/README.md +216 -0
- data/configurator.gemspec +44 -0
- data/lib/configurator/cast.rb +165 -0
- data/lib/configurator/delegated.rb +112 -0
- data/lib/configurator/dsl.rb +139 -0
- data/lib/configurator/errors.rb +56 -0
- data/lib/configurator/extensions.rb +42 -0
- data/lib/configurator/loader.rb +56 -0
- data/lib/configurator/option.rb +281 -0
- data/lib/configurator/section.rb +254 -0
- data/lib/configurator/validation.rb +18 -0
- data/lib/configurator/version.rb +21 -0
- data/lib/configurator.rb +30 -0
- metadata +114 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require 'singleton'
|
20
|
+
require 'monitor'
|
21
|
+
|
22
|
+
module Configurator
|
23
|
+
module Cast
|
24
|
+
class Director
|
25
|
+
class << self
|
26
|
+
def __mutex__
|
27
|
+
@__mutex__ ||= ::Monitor.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def casts
|
31
|
+
if @casts.nil?
|
32
|
+
__mutex__.synchronize { @casts ||= {} }
|
33
|
+
else
|
34
|
+
@casts
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def type_to_key(type)
|
39
|
+
case type
|
40
|
+
when ::Array then "collection:%s" % type.first
|
41
|
+
when ::Proc then "proc:%d" % type.object_id
|
42
|
+
else type.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def acquire(type)
|
47
|
+
type_key = type_to_key(type)
|
48
|
+
return casts[type_key] if casts.include?(type_key)
|
49
|
+
|
50
|
+
__mutex__.synchronize do
|
51
|
+
return casts[type_key] if casts.include?(type_key)
|
52
|
+
|
53
|
+
casts[type_key] = case type
|
54
|
+
when ::Array then
|
55
|
+
Cast::Collection.new(type.first)
|
56
|
+
when :uri, /uri/i then
|
57
|
+
Cast::URI.new
|
58
|
+
when :any, /any/i then
|
59
|
+
Cast::Generic.new
|
60
|
+
when ::Symbol, ::String then
|
61
|
+
begin
|
62
|
+
Cast.const_get(type.to_s.capitalize).new
|
63
|
+
rescue NameError
|
64
|
+
raise InvalidCastType, "Invalid cast type #{type}"
|
65
|
+
end
|
66
|
+
when ::Proc then
|
67
|
+
Cast::Callable.new(type)
|
68
|
+
else
|
69
|
+
raise InvalidCastType, "Invalid cast type #{type}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
casts[type_key]
|
74
|
+
end
|
75
|
+
alias :[] :acquire
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Generic
|
80
|
+
def _cast(value) value; end
|
81
|
+
private :_cast
|
82
|
+
|
83
|
+
def convert(value)
|
84
|
+
begin
|
85
|
+
_cast(value);
|
86
|
+
rescue StandardError => e
|
87
|
+
raise CastError.new(e.message).tap { |exc| exc.set_backtrace(e.backtrace) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Collection < Generic
|
93
|
+
def initialize(subtype)
|
94
|
+
@cast = Director.acquire(subtype)
|
95
|
+
raise ArgumentError, "Collection subtype cannot be another collection" if @cast.is_a? Collection
|
96
|
+
end
|
97
|
+
|
98
|
+
def _cast(value)
|
99
|
+
[*value].collect { |v| @cast.convert(v) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Scalar < Generic; end
|
104
|
+
|
105
|
+
class String < Generic
|
106
|
+
def _cast(value) value.to_s; end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Integer < Generic
|
110
|
+
def _cast(value) value.to_i; end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Float < Generic
|
114
|
+
def _cast(value) value.to_f; end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Symbol < Generic
|
118
|
+
def _cast(value) value.to_s.to_sym; end
|
119
|
+
end
|
120
|
+
|
121
|
+
class URI < Generic
|
122
|
+
def _cast(value) ::URI.parse(value); end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Path < Generic
|
126
|
+
def _cast(value) ::Pathname.new(value); end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Hash < Generic
|
130
|
+
def _cast(value)
|
131
|
+
return value if value.is_a?(::Hash)
|
132
|
+
case value
|
133
|
+
when Array then
|
134
|
+
return Hash[*value] if value.size % 2 == 0
|
135
|
+
raise CastError, "input array value has odd number of elements - unable to convert to Hash"
|
136
|
+
else { value => value }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Array < Generic
|
142
|
+
def _cast(value) [*value] rescue [value]; end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Boolean < Generic
|
146
|
+
def _cast(value)
|
147
|
+
case value
|
148
|
+
when /(off|false|no|disabled?)/ then false
|
149
|
+
when /(on|true|enable|yes)/ then true
|
150
|
+
else !!value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Callable < Generic
|
156
|
+
def initialize(proc)
|
157
|
+
@proc = proc
|
158
|
+
end
|
159
|
+
|
160
|
+
def _cast(value)
|
161
|
+
@proc.call(value)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
module Configurator
|
20
|
+
class OptionValue < SimpleDelegator
|
21
|
+
def initialize(option)
|
22
|
+
@option = option
|
23
|
+
|
24
|
+
case @option.type
|
25
|
+
when :string then
|
26
|
+
self.class.send(:define_method, :to_str) { self.to_s } unless defined? :to_str
|
27
|
+
when :integer then
|
28
|
+
self.class.send(:define_method, :to_int) { self.to_i } unless defined? :to_int
|
29
|
+
end
|
30
|
+
|
31
|
+
super(option.value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(method, *args, &block)
|
35
|
+
begin
|
36
|
+
super
|
37
|
+
rescue NoMethodError
|
38
|
+
begin
|
39
|
+
raise NoMethodError, "undefined method '#{method}' for #{@option.type} option #{path_name}."
|
40
|
+
rescue NoMethodError => e
|
41
|
+
# hack to remove trace information for this file
|
42
|
+
e.backtrace.collect!{ |line| line.include?(__FILE__) ? nil : line}.compact!
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def name; @option.name; end
|
49
|
+
def parent; @option.parent; end
|
50
|
+
def cast; @option.caster.class.name.split('::').last.downcase.to_sym; end
|
51
|
+
def type; @option.type; end
|
52
|
+
def default; @option.default; end
|
53
|
+
def value; self; end
|
54
|
+
|
55
|
+
def root; parent ? parent.root : nil; end
|
56
|
+
def path_name; @option.path_name; end
|
57
|
+
|
58
|
+
def empty?; !option.value.nil? && @option.value.empty?; end
|
59
|
+
def nil?; @option.value.nil?; end
|
60
|
+
def valid?; @option.valid?; end
|
61
|
+
def required?; @option.required?; end
|
62
|
+
def optional?; @option.optional?; end
|
63
|
+
end
|
64
|
+
|
65
|
+
class DelegatedOption < SimpleDelegator
|
66
|
+
attr_accessor :name, :parent
|
67
|
+
private :name=, :parent=
|
68
|
+
|
69
|
+
def initialize(new_name, new_parent, object)
|
70
|
+
@name, @parent = new_name, new_parent
|
71
|
+
super(object)
|
72
|
+
end
|
73
|
+
|
74
|
+
def root() parent.nil? ? self : parent.root; end
|
75
|
+
def path_name()
|
76
|
+
parent.nil? ? name : [ parent.path_name, name ].join('.')
|
77
|
+
end
|
78
|
+
|
79
|
+
def renamed?; self.is_a? RenamedOption; end
|
80
|
+
def deprecated?; self.is_a? DeprecatedOption; end
|
81
|
+
def emit_warning(); end
|
82
|
+
def value() emit_warning; super; end
|
83
|
+
def value=(v) emit_warning; super; end
|
84
|
+
end
|
85
|
+
|
86
|
+
class AliasedOption < DelegatedOption; end
|
87
|
+
|
88
|
+
class RenamedOption < AliasedOption
|
89
|
+
def emit_warning
|
90
|
+
warn "Configuration option #{path_name} was renamed to #{__getobj__.path_name} - please update your configuration"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class DeprecatedOption < DelegatedOption
|
95
|
+
def initialize(name, parent, object, end_of_life = nil)
|
96
|
+
@eol = end_of_life
|
97
|
+
super(name, parent, object)
|
98
|
+
end
|
99
|
+
|
100
|
+
def emit_warning
|
101
|
+
if @eol && !@eol.is_a?(TrueClass)
|
102
|
+
@eol = case @eol
|
103
|
+
when Date, DateTime, Time then @eol.strftime('%F')
|
104
|
+
else @eol
|
105
|
+
end
|
106
|
+
warn "Configuration option #{path_name} is deprecated and will no longer be available on or after #{@eol}."
|
107
|
+
else
|
108
|
+
warn "Configuration option #{path_name} is deprecated and will be removed soon."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require 'singleton'
|
20
|
+
|
21
|
+
module Configurator
|
22
|
+
module DSL
|
23
|
+
extend self
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
base.send :include, Singleton
|
27
|
+
base.send :include, InstanceMethods
|
28
|
+
base.extend self
|
29
|
+
|
30
|
+
# allow for reloading of target class
|
31
|
+
base.class_eval { remove_instance_variable(:@config) if defined? @config }
|
32
|
+
|
33
|
+
base.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
34
|
+
def self.method_missing(method, *args, &block)
|
35
|
+
return super unless instance.respond_to? method
|
36
|
+
instance.public_send(method, *args, &block)
|
37
|
+
end
|
38
|
+
EOF
|
39
|
+
end
|
40
|
+
|
41
|
+
def config(&block)
|
42
|
+
@config ||= Configurator::Section.new(:root)
|
43
|
+
@config.instance_exec(@config, &block) if block_given?
|
44
|
+
@config
|
45
|
+
end
|
46
|
+
alias :configuration :config
|
47
|
+
alias :root :config
|
48
|
+
|
49
|
+
def to_yaml
|
50
|
+
instance.to_yaml
|
51
|
+
end
|
52
|
+
|
53
|
+
alias :_inspect :inspect
|
54
|
+
def inspect
|
55
|
+
s = ''
|
56
|
+
s << "#<#{self.class.name}:0x%x " % (self.__id__ * 2)
|
57
|
+
s << {:config_path => config_path, :config => config}.inject([]) { |a,(k,v)| a << "@#{k}=#{v.inspect}" }.join(', ')
|
58
|
+
s << '>'
|
59
|
+
end
|
60
|
+
|
61
|
+
module InstanceMethods
|
62
|
+
attr_reader :config_path
|
63
|
+
|
64
|
+
def config
|
65
|
+
@config ||= self.class.config
|
66
|
+
end
|
67
|
+
alias :root :config
|
68
|
+
|
69
|
+
def respond_to?(method)
|
70
|
+
return true if config.respond_to? method
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_missing(method, *args, &block)
|
75
|
+
if config.include? method
|
76
|
+
config[method]
|
77
|
+
elsif config.public_methods.include? method
|
78
|
+
config.public_send(method, *args, &block)
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def reload!
|
85
|
+
return false unless config_path
|
86
|
+
return false unless @env
|
87
|
+
config.load loader.reload!(@env)
|
88
|
+
|
89
|
+
unless config.requirements_fullfilled?
|
90
|
+
raise ConfigurationInvalid, "Missing one or more required options."
|
91
|
+
end
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def load(config_path, env)
|
96
|
+
self.tap {
|
97
|
+
@env = env
|
98
|
+
@config_path = config_path
|
99
|
+
config.load loader.load(@env)
|
100
|
+
|
101
|
+
unless config.requirements_fullfilled?
|
102
|
+
raise ConfigurationInvalid, "Missing one or more required options."
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def alias!(orig_path, new_path)
|
108
|
+
config.alias!(orig_path, new_path)
|
109
|
+
end
|
110
|
+
|
111
|
+
def deprecate!(path, end_of_life = nil)
|
112
|
+
config.deprecate!(path, end_of_life)
|
113
|
+
end
|
114
|
+
|
115
|
+
def rename!(old_path, target_path)
|
116
|
+
config.rename!(old_path, target_path)
|
117
|
+
end
|
118
|
+
|
119
|
+
def load_from_hash(hash)
|
120
|
+
config.load hash
|
121
|
+
end
|
122
|
+
|
123
|
+
def loader
|
124
|
+
@loader ||= Configurator::Loader.new(config_path, Kernel.binding)
|
125
|
+
end
|
126
|
+
private :loader
|
127
|
+
|
128
|
+
def to_h
|
129
|
+
@config.to_h
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_yaml
|
133
|
+
@config.to_yaml
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
Bindings = DSL
|
139
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
module Configurator
|
20
|
+
class Error < StandardError; end
|
21
|
+
|
22
|
+
class ValidationError < Error; end
|
23
|
+
class ConfigurationInvalid < Error; end
|
24
|
+
|
25
|
+
class InvalidOptionPath < Error; end
|
26
|
+
|
27
|
+
class CastError < Error; end
|
28
|
+
class InvalidCastType < CastError; end
|
29
|
+
class CastFailure < CastError; end
|
30
|
+
|
31
|
+
class OptionError < Error; end
|
32
|
+
|
33
|
+
class RenameFailed < OptionError; end
|
34
|
+
class DeprecateFailed < OptionError; end
|
35
|
+
class AliasFailed < OptionError; end
|
36
|
+
|
37
|
+
class OptionExists < OptionError; end
|
38
|
+
class OptionNotExist < OptionError; end
|
39
|
+
class OptionInvalid < OptionError; end
|
40
|
+
class OptionInvalidArgument < OptionError; end
|
41
|
+
class OptionInvalidCallableDefault < OptionError; end
|
42
|
+
|
43
|
+
class OptionLoopError < SystemStackError
|
44
|
+
attr_accessor :stack
|
45
|
+
|
46
|
+
def initialize(*args)
|
47
|
+
@stack = []
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"Loop detected in #{stack.first}. Request Stack: #{stack.join(' -> ')}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class String
|
4
|
+
def option_path_split(sep = '.')
|
5
|
+
return self unless self.count(sep) > 0
|
6
|
+
[ (parts = self.split(sep))[0..-2].join(sep), parts.last ]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Hash
|
11
|
+
def stringify_keys!
|
12
|
+
self.replace(inject({}) { |h,(k,v)|
|
13
|
+
h.tap { h[k.to_s] = v.stringify_keys! rescue v }
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
def stringify_keys
|
18
|
+
self.dup.stringify_keys!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Psych
|
23
|
+
module Visitors
|
24
|
+
class YAMLTree < Psych::Visitors::Visitor
|
25
|
+
def visit_Configurator_DelegatedOption(target)
|
26
|
+
send(@dispatch_cache[target.value.class], target.value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_Configurator_Section(target)
|
30
|
+
send(@dispatch_cache[target.table.class], target.table.stringify_keys)
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_Configurator_Option(target)
|
34
|
+
send(@dispatch_cache[target.value.class], target.value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_Pathname(target)
|
38
|
+
send(@dispatch_cache[target.to_s.class], (target.realpath rescue taret).to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2013 Carl P. Corliss
|
3
|
+
|
4
|
+
This program is free software; you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation; either version 2 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License along
|
15
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require 'pathname'
|
20
|
+
require 'erb'
|
21
|
+
require 'yaml'
|
22
|
+
|
23
|
+
module Configurator
|
24
|
+
class Loader
|
25
|
+
attr_reader :data, :path
|
26
|
+
private :data
|
27
|
+
|
28
|
+
def initialize(path, _binding = nil)
|
29
|
+
@path = Pathname.new(path).realpath
|
30
|
+
@binding = _binding
|
31
|
+
@data = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](env)
|
35
|
+
data[env]
|
36
|
+
end
|
37
|
+
|
38
|
+
def data(reload = false)
|
39
|
+
@data = nil if reload
|
40
|
+
|
41
|
+
@data ||= unless @binding.nil?
|
42
|
+
YAML::load(ERB.new(IO.read(@path.to_s)).result(@binding))
|
43
|
+
else
|
44
|
+
YAML::load(ERB.new(IO.read(@path.to_s)).result)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def load(environment)
|
49
|
+
data[environment.to_s]
|
50
|
+
end
|
51
|
+
|
52
|
+
def reload!(environment)
|
53
|
+
data(true)[environment.to_s]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|