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.
@@ -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