rabbitt-configurator 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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