rabbitt-configurator 1.2.4
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/.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
|