calibrate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6a9efa272faf23fed111b7446d58878d2534bace
4
+ data.tar.gz: c13706c1ddaf9ed6a3298e4b55e1e4f491a1e208
5
+ SHA512:
6
+ metadata.gz: 9b3895aa4322307c3f8d1145adf7004b103008c12f50b3e1566b4c8292161962fc2f281b7d3c58a12fc17a9e8a3ade56cc059cbf79f71c39b8b27a258cd3f141
7
+ data.tar.gz: e0ac1bc4a4be8dead4626ee2296e00330cd788df779ca82a47d874b4bbcf4615125e43ac2fa9a2015db5f501f7f8062ec64b4cffbed5b6222188cc90d11d664d
data/lib/calibrate.rb ADDED
@@ -0,0 +1 @@
1
+ require 'calibrate/configurable'
@@ -0,0 +1,29 @@
1
+ module Calibrate
2
+ #Handles setting options on objects it's mixed into
3
+ #
4
+ #Settings can have default values or be required (as opposed to defaulting to
5
+ #nil). Settings and their defaults are inherited (and can be overridden) by
6
+ #subclasses.
7
+ #
8
+ #Calibrate also includes a yard-extension that will document settings of a
9
+ #Configurable
10
+ #
11
+ #@example (see ClassMethods)
12
+ module Configurable
13
+ class Exception < ::StandardError
14
+ end
15
+
16
+ class NoDefaultValue < Exception
17
+ def initialize(field_name, klass)
18
+ super("No default value for field #{field_name} on class #{klass.name}")
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'calibrate/configurable/field-metadata'
25
+ require 'calibrate/configurable/proxy-value'
26
+ require 'calibrate/configurable/field-processor'
27
+ require 'calibrate/configurable/class-methods'
28
+ require 'calibrate/configurable/instance-methods'
29
+ require 'calibrate/configurable/directory-structure'
@@ -0,0 +1,227 @@
1
+ module Calibrate
2
+ module Configurable
3
+ RequiredField = Object.new.freeze
4
+
5
+ #Describes class level DSL & machinery for working with configuration
6
+ #managment.
7
+ #
8
+ #@example
9
+ # class ConfExample
10
+ # include Configurable
11
+ #
12
+ # setting :foo
13
+ # settings :bar => 1, :baz => 3
14
+ # nil_fields :hoo, :ha, :harum
15
+ # required_fields :must
16
+ #
17
+ # def initialize
18
+ # setup_defaults
19
+ # end
20
+ # end
21
+ #
22
+ # ce = ConfExample.new
23
+ # ce.bar #=> 1
24
+ # ce.hoo #=> nil
25
+ # ce.hoo = "hallo"
26
+ # ce.check_required #=> raises error because :must and :foo aren't set
27
+ module ClassMethods
28
+ def inspect_instance(instance, indent="")
29
+ field_names.map do |name|
30
+ meta = field_metadata(name)
31
+ "#{indent}#{meta.inspect_on(instance, indent * 2)}"
32
+ end.join("\n")
33
+ end
34
+
35
+ def default_values
36
+ @default_values ||= []
37
+ end
38
+
39
+ def field_names
40
+ names = default_values.map{|field| field.name}
41
+ if Configurable > superclass
42
+ names | superclass.field_names
43
+ else
44
+ names
45
+ end
46
+ end
47
+
48
+ def field_metadata(name)
49
+ field = default_values.find{|field| field.name == name}
50
+ if field.nil? and Configurable > superclass
51
+ superclass.field_metadata(name)
52
+ else
53
+ field
54
+ end
55
+ end
56
+
57
+ #@raises NoDefaultValue
58
+ def default_value_for(name)
59
+ field = field_metadata(name)
60
+ raise NoDefaultValue.new(name,self) unless field.is?(:defaulting)
61
+ return field.default_value
62
+ end
63
+
64
+ #Creates an anonymous Configurable - useful in complex setups for nested
65
+ #settings
66
+ #@example SSH options
67
+ # setting :ssh => nested(:username => "me", :password => nil)
68
+ def nested(hash=nil, &block)
69
+ nested = Class.new(Struct)
70
+ nested.settings(hash || {})
71
+ if block_given?
72
+ nested.instance_eval(&block)
73
+ end
74
+ return nested
75
+ end
76
+
77
+ #Quick list of setting fields with a default value of nil. Useful
78
+ #especially with {CascadingDefinition#resolve_configuration}
79
+ def nil_fields(*names)
80
+ names.each do |name|
81
+ setting(name, nil)
82
+ end
83
+ self
84
+ end
85
+ alias nil_field nil_fields
86
+
87
+ #List fields with no default for with a value must be set before
88
+ #definition.
89
+ def required_fields(*names)
90
+ names.each do |name|
91
+ setting(name)
92
+ end
93
+ self
94
+ end
95
+ alias required_field required_fields
96
+
97
+ #Defines a setting on this class - much like a attr_accessible call, but
98
+ #allows for defaults and required settings
99
+ def setting(name, default_value = RequiredField)
100
+ name = name.to_sym
101
+ metadata =
102
+ if default_value == RequiredField
103
+ FieldMetadata.new(name, nil).is(:required).isnt(:defaulting)
104
+ else
105
+ FieldMetadata.new(name, default_value)
106
+ end
107
+
108
+ attr_writer(name)
109
+ define_method(metadata.reader_method) do
110
+ metadata.value_on(self)
111
+ end
112
+
113
+ if existing = default_values.find{|field| field.name == name} and existing.default_value != default_value
114
+ source_line = caller.drop_while{|line| /#{__FILE__}/ =~ line}.first
115
+ warn "Changing default value of #{self.name}##{name} from #{existing.default_value.inspect} to #{default_value.inspect} (at: #{source_line})"
116
+ end
117
+ default_values << metadata
118
+ metadata
119
+ end
120
+
121
+ def runtime_required_fields(*names)
122
+ names.each do |name|
123
+ runtime_setting(name)
124
+ end
125
+ self
126
+ end
127
+ alias runtime_required_field runtime_required_fields
128
+
129
+ def runtime_setting(name, default_value = RequiredField)
130
+ setting(name, default_value).is(:runtime)
131
+ end
132
+
133
+ #@param [Hash] hash Pairs of name/value to be converted into
134
+ # setting/default
135
+ def settings(hash)
136
+ hash.each_pair do |name, value|
137
+ setting(name, value)
138
+ end
139
+ return self
140
+ end
141
+ alias runtime_settings settings
142
+
143
+ def set_defaults_on(instance)
144
+ if Configurable > superclass
145
+ superclass.set_defaults_on(instance)
146
+ end
147
+ default_values.each do |field|
148
+ next unless field.is? :defaulting
149
+ value = field.build_default_value
150
+ instance.__send__(field.writer_method, value)
151
+ end
152
+ end
153
+
154
+ def missing_required_fields_on(instance)
155
+ missing = []
156
+ if Configurable > superclass
157
+ missing = superclass.missing_required_fields_on(instance)
158
+ end
159
+ default_values.each do |field|
160
+ if field.missing_on?(instance)
161
+ missing << field.name
162
+ else
163
+ set_value = instance.__send__(field.reader_method)
164
+ if Configurable === set_value
165
+ missing += set_value.class.missing_required_fields_on(set_value).map do |sub_field|
166
+ [field.name, sub_field].join(".")
167
+ end
168
+ end
169
+ end
170
+ end
171
+ return missing
172
+ end
173
+
174
+ def to_hash(obj)
175
+ hash = if Configurable > superclass
176
+ superclass.to_hash(obj)
177
+ else
178
+ {}
179
+ end
180
+ hash.merge( Hash[default_values.map{|field|
181
+ begin
182
+ value = obj.__send__(field.reader_method)
183
+ value =
184
+ case value
185
+ when Configurable
186
+ value.to_hash
187
+ else
188
+ value
189
+ end
190
+ [field.name, value]
191
+ rescue NoMethodError
192
+ end
193
+ }])
194
+ end
195
+
196
+ def from_hash(obj, hash) #XXX It'd be really nice if this could report unused fields
197
+ if Configurable > superclass
198
+ superclass.from_hash(obj, hash)
199
+ end
200
+ default_values.each do |field|
201
+ catch :next do
202
+ key = field.reader_method.to_s
203
+ value = hash.fetch(key.to_s) do
204
+ key = key.to_sym
205
+ hash.fetch(key) do
206
+ throw :next
207
+ end
208
+ end
209
+
210
+ existing_value = obj.__send__(field.reader_method)
211
+ if Configurable === existing_value and value.is_a? Hash
212
+ existing_value.from_hash(value)
213
+ else
214
+ obj.__send__(field.writer_method, value)
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ def included(mod)
221
+ mod.extend ClassMethods
222
+ end
223
+ end
224
+
225
+ extend ClassMethods
226
+ end
227
+ end
@@ -0,0 +1,163 @@
1
+ require 'rake'
2
+
3
+ module Calibrate
4
+ module Configurable
5
+ class MissingRelativePaths < Exception; end
6
+
7
+ #XXX Consider making the actual dir/path settings r/o
8
+ #Very easy to say
9
+ # filename = "string"
10
+ #rather than
11
+ # filename.relative_path = "string"
12
+ #and it isn't clear which (abs/rel) you mean
13
+ #
14
+ module DirectoryStructure
15
+ class StructurePath
16
+ include Configurable
17
+
18
+ setting :absolute_path
19
+ setting :relative_path
20
+
21
+ alias abspath absolute_path
22
+ alias relpath relative_path
23
+
24
+ #No #path - ambiguous whether that would be abspath or pathname
25
+
26
+ def initialize(rel_path)
27
+ self.relative_path = rel_path unless rel_path == Configurable::RequiredField
28
+ end
29
+
30
+ def pathname
31
+ @pathname ||=
32
+ begin
33
+ fail_unless_set(:absolute_path)
34
+ require 'pathname'
35
+ Pathname.new(absolute_path)
36
+ end
37
+ end
38
+ alias path_name pathname
39
+
40
+ def to_s
41
+ fail_unless_set(:absolute_path)
42
+ absolute_path
43
+ end
44
+
45
+ def inspect
46
+ "<path: #{
47
+ if field_unset?(:absolute_path)
48
+ if field_unset?(:relative_path)
49
+ "<<?>>"
50
+ else
51
+ "?/#{relative_path}"
52
+ end
53
+ else
54
+ absolute_path.inspect
55
+ end
56
+ }>"
57
+ end
58
+ end
59
+
60
+ module ClassMethods
61
+ RequiredField = Configurable::RequiredField
62
+
63
+ def root_paths
64
+ @root_paths ||= []
65
+ end
66
+
67
+ def path_heirarchy
68
+ @path_heirarchy ||= []
69
+ end
70
+ attr_writer :path_heirarchy
71
+
72
+ def path_fields
73
+ @path_fields ||= []
74
+ end
75
+
76
+ def dir(field_name, *args)
77
+ rel_path = RequiredField
78
+ if String === args.first
79
+ rel_path = args.shift
80
+ end
81
+ parent_field = path(field_name, rel_path)
82
+
83
+ self.path_heirarchy += args.map do |child_field|
84
+ [parent_field, child_field]
85
+ end
86
+ return parent_field
87
+ end
88
+
89
+ def path(field_name, rel_path=RequiredField)
90
+ field = setting(field_name, StructurePath.new(rel_path))
91
+ root_paths << field
92
+ path_fields << field
93
+ return field
94
+ end
95
+
96
+ def resolve_path_on(instance, parent, child_field, missing_relatives)
97
+ child = child_field.value_on(instance)
98
+ return unless child.field_unset?(:absolute_path)
99
+ if child.field_unset?(:relative_path)
100
+ missing_relatives << child_field
101
+ return
102
+ end
103
+ child.absolute_path = File::join(parent.absolute_path, child.relative_path)
104
+ end
105
+
106
+ def resolve_paths_on(instance)
107
+ superclass_exception = nil
108
+ if superclass < DirectoryStructure
109
+ begin
110
+ superclass.resolve_paths_on(instance)
111
+ rescue MissingRelativePaths => mrp
112
+ superclass_exception = mrp
113
+ end
114
+ end
115
+ missing_relatives = []
116
+
117
+ (root_paths - path_heirarchy.map{|_, child| child }).each do |field|
118
+ resolve_path_on(instance, instance, field, missing_relatives)
119
+ end
120
+
121
+ path_heirarchy.reverse.each do |parent_field, child_field|
122
+ next if missing_relatives.include?(parent_field)
123
+ parent = parent_field.value_on(instance)
124
+ resolve_path_on(instance, parent, child_field, missing_relatives)
125
+ end
126
+
127
+ case [missing_relatives.empty?, superclass_exception.nil?]
128
+ when [true, false]
129
+ raise superclass_exception
130
+ when [false, true]
131
+ raise MissingRelativePaths, "Required field#{missing_relatives.length == 1 ? "" : "s"} #{missing_relatives.map{|field| "#{field.name}.relative_path".inspect}.join(", ")} unset on #{self.inspect}"
132
+ when [false, false]
133
+ raise MissingRelativePaths, "Required field#{missing_relatives.length == 1 ? "" : "s"} #{missing_relatives.map{|field| "#{field.name}.relative_path".inspect}.join(", ")} unset on #{self.inspect}" + "\n" + superclass_exception.message
134
+ end
135
+
136
+ path_fields.each do |field|
137
+ value = field.value_on(instance)
138
+ next unless value.field_unset?(:relative_path)
139
+ value.relative_path = value.absolute_path
140
+ end
141
+ end
142
+ end
143
+
144
+ def self.included(sub)
145
+ sub.extend ClassMethods
146
+ dir_path =
147
+ if not (file_path = ::Rake.application.rakefile).nil?
148
+ File::dirname(File::expand_path(file_path))
149
+ elsif not (dir_path = ::Rake.application.original_dir).nil?
150
+ dir_path
151
+ else
152
+ file_path = caller[0].split(':')[0]
153
+ File::dirname(File::expand_path(file_path))
154
+ end
155
+ sub.setting :absolute_path, dir_path
156
+ end
157
+
158
+ def resolve_paths
159
+ self.class.resolve_paths_on(self)
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,157 @@
1
+ module Calibrate
2
+ module Configurable
3
+ class FieldMetadata
4
+ attr_accessor :name, :default_value
5
+
6
+ DEFAULT_PROPERTIES = {
7
+ :copiable => true,
8
+ :proxiable => true,
9
+ :required => false,
10
+ :runtime => false,
11
+ :defaulting => true,
12
+ }
13
+ def initialize(name, value)
14
+ @name = name
15
+ @default_value = value
16
+ @properties = DEFAULT_PROPERTIES.clone
17
+ end
18
+
19
+ def inspect
20
+ set_props = DEFAULT_PROPERTIES.keys.find_all do |prop|
21
+ @properties[prop]
22
+ end
23
+ "Field: #{name}: #{default_value.inspect} #{set_props.inspect}"
24
+ end
25
+
26
+ def inspect_on(instance, indent=nil)
27
+ set_props = DEFAULT_PROPERTIES.keys.find_all do |prop|
28
+ @properties[prop]
29
+ end
30
+ "Field: #{name}: #{value_on(instance).inspect} \n#{indent||""}(default: #{default_value.inspect} immediate: #{immediate_value_on(instance).inspect}) #{set_props.inspect}"
31
+ end
32
+
33
+ def validate_property_name(name)
34
+ unless DEFAULT_PROPERTIES.has_key?(name)
35
+ raise "Invalid field property #{name.inspect} - valid are: #{DEFAULT_PROPERTIES.keys.inspect}"
36
+ end
37
+ end
38
+
39
+ def is?(property)
40
+ validate_property_name(property)
41
+ @properties[property]
42
+ end
43
+
44
+ def is_not?(property)
45
+ validate_property_name(property)
46
+ !@properties[property]
47
+ end
48
+ alias isnt? is_not?
49
+
50
+ def is(property)
51
+ validate_property_name(property)
52
+ @properties[property] = true
53
+ self
54
+ end
55
+
56
+ def is_not(property)
57
+ validate_property_name(property)
58
+ @properties[property] = false
59
+ self
60
+ end
61
+ alias isnt is_not
62
+
63
+ def ivar_name
64
+ "@#{name}"
65
+ end
66
+
67
+ def writer_method
68
+ "#{name}="
69
+ end
70
+
71
+ def reader_method
72
+ name
73
+ end
74
+
75
+ def copy_from(instance)
76
+ return if unset_on?(instance)
77
+ copy_value(immediate_value_on(instance))
78
+ end
79
+
80
+ def build_default_value
81
+ if Module === @default_value and Configurable > @default_value
82
+ value = @default_value.new
83
+ value.class.set_defaults_on(value)
84
+ value
85
+ else
86
+ copy_value(@default_value)
87
+ end
88
+ end
89
+
90
+ def copy_value(value)
91
+ case value
92
+ when Symbol, Numeric, NilClass, TrueClass, FalseClass
93
+ value
94
+ else
95
+ if value.class == BasicObject
96
+ value
97
+ elsif value.respond_to?(:dup)
98
+ value.dup
99
+ elsif value.respond_to?(:clone)
100
+ value.clone
101
+ else
102
+ value
103
+ end
104
+ end
105
+ end
106
+
107
+ def immediate_value_on(instance)
108
+ instance.instance_variable_get(ivar_name)
109
+ #instance.__send__(reader_method)
110
+ end
111
+
112
+ def value_on(instance)
113
+ value = immediate_value_on(instance)
114
+ if ProxyValue === value
115
+ value.field.value_on(value.source)
116
+ else
117
+ value
118
+ end
119
+ end
120
+
121
+ def set_on?(instance)
122
+ return true unless instance.__send__(reader_method).nil?
123
+ return false unless instance.instance_variable_defined?(ivar_name)
124
+ value = immediate_value_on(instance)
125
+ if ProxyValue === value
126
+ value.field.set_on?(value.source)
127
+ else
128
+ true
129
+ end
130
+ end
131
+
132
+ def unset_on?(instance)
133
+ !set_on?(instance)
134
+ end
135
+
136
+ def missing_on?(instance)
137
+ return false unless is?(:required)
138
+ if instance.respond_to?(:runtime?) and !instance.runtime?
139
+ return runtime_missing_on?(instance)
140
+ else
141
+ return !set_on?(instance)
142
+ end
143
+ end
144
+
145
+ def runtime_missing_on?(instance)
146
+ return false if is?(:runtime)
147
+ return true unless instance.instance_variable_defined?(ivar_name)
148
+ value = immediate_value_on(instance)
149
+ if ProxyValue === value
150
+ value.field.runtime_missing_on?(value.source)
151
+ else
152
+ false
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,54 @@
1
+ module Calibrate
2
+ module Configurable
3
+ class FieldProcessor
4
+ def initialize(source)
5
+ @source = source
6
+ @field_names = filter(source.class.field_names)
7
+ end
8
+ attr_accessor :field_names
9
+ attr_reader :source
10
+
11
+ def filter(field_names)
12
+ field_names.find_all do |name|
13
+ source.class.field_metadata(name).is?(filter_attribute)
14
+ end
15
+ end
16
+
17
+ def can_process(field, target)
18
+ target.respond_to?(field.writer_method)
19
+ end
20
+
21
+ def to(target)
22
+ field_names.each do |name|
23
+ field = source.class.field_metadata(name)
24
+ next unless can_process(field, target)
25
+ target.__send__(field.writer_method, value(field))
26
+ end
27
+ end
28
+ end
29
+
30
+ class SettingsCopier < FieldProcessor
31
+ def filter_attribute
32
+ :copiable
33
+ end
34
+
35
+ def can_process(field, target)
36
+ super and not( field.unset_on?(source) and field.unset_on?(target) )
37
+ end
38
+
39
+ def value(field)
40
+ return field.copy_from(source)
41
+ end
42
+ end
43
+
44
+ class SettingsProxier < FieldProcessor
45
+ def filter_attribute
46
+ :proxiable
47
+ end
48
+
49
+ def value(field)
50
+ ProxyValue.new(source, field)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,90 @@
1
+ require 'calibrate/configurable/directory-structure'
2
+
3
+ module Calibrate
4
+ class RequiredFieldUnset < StandardError
5
+ end
6
+
7
+ module Configurable
8
+ def initialize_copy(original)
9
+ original.copy_settings_to(self)
10
+ end
11
+
12
+ def copy_settings
13
+ SettingsCopier.new(self)
14
+ end
15
+
16
+ def copy_settings_to(other)
17
+ copy_settings.to(other)
18
+ self
19
+ end
20
+
21
+ def proxy_settings
22
+ SettingsProxier.new(self)
23
+ end
24
+
25
+ def proxy_settings_to(other)
26
+ proxy_settings.to(other)
27
+ end
28
+
29
+ def to_hash
30
+ self.class.to_hash(self)
31
+ end
32
+
33
+ def from_hash(hash)
34
+ self.class.from_hash(self, hash)
35
+ end
36
+
37
+ def unset_defaults_guard
38
+ raise "Tried to check required settings before running setup_defaults"
39
+ end
40
+
41
+ #Call during initialize to set default values on settings - if you're using
42
+ #Configurable outside of Calibrate, be sure this gets called.
43
+ def setup_defaults
44
+ def self.unset_defaults_guard
45
+ end
46
+
47
+ self.class.set_defaults_on(self)
48
+ self
49
+ end
50
+
51
+ #Checks that all required fields have be set, otherwise raises an error
52
+ #@raise RuntimeError if any required fields are unset
53
+ def check_required
54
+ unset_defaults_guard
55
+ missing = self.class.missing_required_fields_on(self)
56
+ unless missing.empty?
57
+ raise RequiredFieldUnset, "Required field#{missing.length > 1 ? "s" : ""} #{missing.map{|field| field.to_s.inspect}.join(", ")} unset on #{self.inspect}"
58
+ end
59
+ self
60
+ end
61
+
62
+ def proxy_value
63
+ ProxyDecorator.new(self)
64
+ end
65
+
66
+ #XXX deprecate
67
+ def unset?(value)
68
+ warn "#unset? is deprecated - use field_unset? instead"
69
+ value.nil?
70
+ end
71
+
72
+ def field_unset?(name)
73
+ self.class.field_metadata(name).unset_on?(self)
74
+ end
75
+
76
+ #Requires that a named field be set
77
+ def fail_unless_set(name)
78
+ if field_unset?(name)
79
+ raise "Assertion failed: Field #{name} unset"
80
+ end
81
+ true
82
+ end
83
+ alias fail_if_unset fail_unless_set
84
+
85
+ class Struct
86
+ include Configurable
87
+ include Configurable::DirectoryStructure
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,30 @@
1
+ module Calibrate
2
+ module Configurable
3
+ class ProxyValue
4
+ def initialize(source, field)
5
+ @source, @field = source, field
6
+ end
7
+ attr_reader :source, :field
8
+
9
+ def inspect
10
+ "#{self.class.name.split(':').last}: #{source.class.name}.#{field.inspect}"
11
+ end
12
+ end
13
+
14
+ class ProxyDecorator
15
+ def initialize(configurable)
16
+ @configurable = configurable
17
+ end
18
+
19
+ def method_missing(name, *args, &block)
20
+ unless block.nil? and args.empty?
21
+ raise NoMethodError, "method `#{name}' not defined with arguments or block when proxied"
22
+ end
23
+ unless @configurable.respond_to?(name)
24
+ raise NoMethodError, "cannot proxy `#{name}' - undefined on #{@configurable}"
25
+ end
26
+ return ProxyValue.new(@configurable, @configurable.class.field_metadata(name))
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,190 @@
1
+ require 'yard'
2
+ YARD::Templates::Engine.register_template_path File::expand_path("../../../yard_templates", __FILE__)
3
+
4
+ module Mattock
5
+ module YARDExtensions
6
+ class DefineHandler < YARD::Handlers::Ruby::Base
7
+ handles :def
8
+
9
+ def mattock_defining?(obj, method)
10
+ check_list = obj.inheritance_tree
11
+ until check_list.empty?
12
+ check_list.each do |co|
13
+ return true if [:CascadingDefinition, :Configurable, :Tasklib, :TaskLib].include? co.name and method == "define"
14
+ return true if [:TaskMixin, :Task, :FileTask, :MultiTask].include? co.name and method == "action"
15
+ end
16
+ check_list = (check_list.find_all{|co| co.respond_to?(:mixins)}||[]).map{|co| co.mixins}.flatten
17
+ end
18
+ end
19
+
20
+ def root
21
+ ns = namespace
22
+ until ns.root?
23
+ ns = ns.namespace
24
+ end
25
+ ns
26
+ end
27
+
28
+ def process
29
+ return unless mattock_defining?(namespace, statement[0][0])
30
+ (root[:tasklibs] ||= []) << namespace
31
+ namespace[:task_definition] = statement[2]
32
+ end
33
+ end
34
+
35
+ class SettingHandler < YARD::Handlers::Ruby::Base
36
+ include YARD::Parser::Ruby
37
+
38
+ handles method_call(:setting)
39
+ namespace_only
40
+
41
+ def mattock_configurable?(obj)
42
+ check_list = obj.inheritance_tree
43
+ until check_list.empty?
44
+ check_list.each do |co|
45
+ return true if [:CascadingDefinition, :Configurable, :Task, :Tasklib, :TaskLib].include? co.name
46
+ end
47
+ check_list = check_list.find_all{|co| co.respond_to?(:mixins)}.map{|co| co.mixins}.flatten
48
+ end
49
+ end
50
+
51
+ def extract_name(obj)
52
+ case obj.type
53
+ when :symbol_literal
54
+ obj.jump(:ident, :op, :kw, :const)[0]
55
+ when :string_literal
56
+ obj.jump(:tstring_content)[0]
57
+ else
58
+ raise YARD::Parser::UndocumentableError, obj.source
59
+ end
60
+ end
61
+
62
+ def append_name(sexp, name)
63
+ prefix = sexp.jump(:ident, :tstring_content)
64
+ if prefix == sexp
65
+ raise YARD::Parser::UndocumentableError, sexp.source
66
+ end
67
+
68
+ "#{prefix[0]}.#{name}"
69
+ end
70
+
71
+ def setting_method_name
72
+ "setting"
73
+ end
74
+
75
+ def synthetic_setting(name, value=nil)
76
+ args = s( s(:string_literal, s(:string_content, s(:tstring_content, name))))
77
+ args << value unless value.nil?
78
+ args << false
79
+ new_call = s(:fcall, s(:ident, setting_method_name), s(:arg_paren, args))
80
+ new_call.line_range = (1..1)
81
+ new_call.traverse do |node|
82
+ node.full_source ||= ""
83
+ end
84
+ new_call.full_source = "#{setting_method_name}('#{name}'#{value.nil? ? "" : ", #{value.source}"})"
85
+ new_call
86
+ end
87
+
88
+ def process
89
+ return unless mattock_configurable?(namespace)
90
+
91
+ #filter further based on NS === Configurable...
92
+ name = extract_name(statement.parameters.first)
93
+
94
+ value = statement.parameters(false)[1]
95
+ if !value.nil? and value.type == :fcall and value.jump(:ident)[0] == "nested"
96
+ remapped = (value.parameters.first||[]).map do |assoc|
97
+ new_name =
98
+ append_name(statement.parameters[0], extract_name(assoc[0]))
99
+ synthetic_setting(new_name, assoc[1])
100
+ end
101
+ parser.process(remapped)
102
+ return
103
+ end
104
+
105
+ setting = YARD::CodeObjects::MethodObject.new(namespace, name) do |set|
106
+ unless value.nil?
107
+ set['default_value'] = statement.parameters(false)[1].source
108
+ end
109
+ set.signature = "def #{name}"
110
+ if statement.comments.to_s.empty?
111
+ set.docstring = "The value of setting #{name}"
112
+ else
113
+ set.docstring = statement.comments
114
+ end
115
+
116
+ set.dynamic = true
117
+ end
118
+
119
+ register setting
120
+ (namespace[:settings] ||= []) << setting
121
+ end
122
+ end
123
+
124
+ class SettingsHandler < SettingHandler
125
+ handles method_call(:settings)
126
+ namespace_only
127
+
128
+ def process
129
+ return unless mattock_configurable?(namespace)
130
+
131
+ remapped = statement.parameters(false).first.map do |assoc|
132
+ synthetic_setting(extract_name(assoc[0]), assoc[1])
133
+ end
134
+ parser.process(remapped)
135
+ end
136
+ end
137
+
138
+ class NilFieldsHandler < SettingHandler
139
+ handles method_call(:nil_field)
140
+ handles method_call(:nil_fields)
141
+ namespace_only
142
+
143
+ def a_nil
144
+ v = s(:kw, nil)
145
+ v.full_source = "nil"
146
+ v
147
+ end
148
+
149
+ def process
150
+ return unless mattock_configurable?(namespace)
151
+ remapped = statement.parameters(false).map do |name|
152
+ synthetic_setting(extract_name(name), a_nil)
153
+ end
154
+ parser.process(remapped)
155
+ end
156
+ end
157
+
158
+ class RuntimeRequiredFieldsHandler < SettingHandler
159
+ handles method_call(:runtime_required_field)
160
+ handles method_call(:runtime_required_fields)
161
+ namespace_only
162
+
163
+ def setting_method_name
164
+ "runtime_setting"
165
+ end
166
+
167
+ def process
168
+ return unless mattock_configurable?(namespace)
169
+ remapped = statement.parameters(false).map do |name|
170
+ synthetic_setting(extract_name(name))
171
+ end
172
+ parser.process(remapped)
173
+ end
174
+ end
175
+
176
+ class RequiredFieldsHandler < SettingHandler
177
+ handles method_call(:required_field)
178
+ handles method_call(:required_fields)
179
+ namespace_only
180
+
181
+ def process
182
+ return unless mattock_configurable?(namespace)
183
+ remapped = statement.parameters(false).map do |name|
184
+ synthetic_setting(extract_name(name))
185
+ end
186
+ parser.process(remapped)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,270 @@
1
+ require 'calibrate'
2
+
3
+ describe Calibrate::Configurable do
4
+ class TestSuperStruct
5
+ include Calibrate::Configurable
6
+
7
+ setting(:three, 3)
8
+ required_field(:four)
9
+ required_field(:override)
10
+ end
11
+
12
+ class TestStruct < TestSuperStruct
13
+ settings(:one => 1, :two => nested(:a => "a"){ required_field(:b)} )
14
+ nil_field(:five)
15
+
16
+ def override
17
+ 17
18
+ end
19
+ end
20
+
21
+ subject do
22
+ TestStruct.new.setup_defaults
23
+ end
24
+
25
+ it "should set defaults" do
26
+ expect(subject.one).to eq(1)
27
+ expect(subject.two.a).to eq("a")
28
+ expect(subject.three).to eq(3)
29
+ expect(subject.five).to be_nil
30
+ end
31
+
32
+ it "#to_hash" do
33
+ hash = subject.to_hash
34
+ expect(hash[:one]).to eq(1)
35
+ expect(hash[:two][:a]).to eq("a")
36
+ end
37
+
38
+ it "#from_hash" do
39
+ subject.from_hash({:one => 111, "two" => { :a => "aaa" }})
40
+
41
+ expect(subject.one).to eq(111)
42
+ expect(subject.two.a).to eq("aaa")
43
+ end
44
+
45
+ it "should complain about unset required fields" do
46
+ expect do
47
+ subject.check_required
48
+ end.to raise_error(Calibrate::RequiredFieldUnset)
49
+ end
50
+
51
+ it "should complain about unset nested required fields" do
52
+ subject.four = 4
53
+ expect do
54
+ subject.check_required
55
+ end.to raise_error(Calibrate::RequiredFieldUnset)
56
+ end
57
+
58
+ it "should not complain when required fields are set" do
59
+ subject.four = 4
60
+ subject.two.b = "b"
61
+ expect do
62
+ subject.check_required
63
+ end.to_not raise_error
64
+ expect(subject.override).to eq(17)
65
+ end
66
+
67
+ it "should inspect cleanly" do
68
+ expect(subject.inspect).to be_a(String)
69
+ end
70
+
71
+ describe "with DirectoryStructure" do
72
+ class DirectoryThing
73
+ include Calibrate::Configurable
74
+ include DirectoryStructure
75
+
76
+ dir(:ephemeral_mountpoint,
77
+ dir(:bundle_workdir, "bundle_workdir",
78
+ path(:bundle_manifest),
79
+ path(:credentials_archive, "aws-creds.tar.gz"),
80
+ dir(:credentials_dir, "aws-creds",
81
+ path(:private_key_file, "pk.pem"),
82
+ path(:certificate_file, "cert.pem")
83
+ )
84
+ )
85
+ )
86
+
87
+ dir(:next_to_me, "rainbow", dir(:in_there, "a_place", path(:nearby, "a.file")))
88
+
89
+ path(:loose_path, "here")
90
+ end
91
+
92
+ describe "distinctness" do
93
+ let :one do
94
+ DirectoryThing.new.tap do |thing|
95
+ thing.setup_defaults
96
+ end
97
+ end
98
+
99
+ let :other do
100
+ DirectoryThing.new.tap do |thing|
101
+ thing.setup_defaults
102
+ end
103
+ end
104
+
105
+ it "should have same values" do
106
+ expect(one.bundle_workdir.relative_path).to eq(other.bundle_workdir.relative_path)
107
+ end
108
+
109
+ it "should have different actual objects" do
110
+ expect(one.bundle_workdir.relative_path).to_not equal other.bundle_workdir.relative_path
111
+ expect(one.bundle_workdir).to_not equal other.bundle_workdir
112
+ end
113
+
114
+ end
115
+
116
+ def subject
117
+ DirectoryThing.new.tap do |thing|
118
+ thing.setup_defaults
119
+ end
120
+ end
121
+
122
+ it "should complain about missing fields" do
123
+ expect do
124
+ subject.check_required
125
+ end.to raise_error(/Required field/)
126
+ end
127
+
128
+ it "should inspect cleanly" do
129
+ expect(subject.inspect).to be_a(String)
130
+ end
131
+
132
+ describe "with root path configured, but missing a relative path" do
133
+ def subject
134
+ DirectoryThing.new.tap do |thing|
135
+ thing.setup_defaults
136
+ thing.ephemeral_mountpoint.absolute_path = "/tmp"
137
+ thing.resolve_paths
138
+ end
139
+ end
140
+
141
+ it "should complain about missing fields" do
142
+ expect do
143
+ subject.check_required
144
+ end.to raise_error(/Required field/)
145
+ end
146
+ end
147
+
148
+ describe "with required paths configured" do
149
+ subject :thing do
150
+ DirectoryThing.new.tap do |thing|
151
+ thing.setup_defaults
152
+ thing.ephemeral_mountpoint.absolute_path = "/tmp"
153
+ thing.bundle_manifest.relative_path = "image.manifest.xml"
154
+ thing.resolve_paths
155
+ end
156
+ end
157
+
158
+ it "should not complain about required fields" do
159
+ expect do
160
+ subject.check_required
161
+ end.not_to raise_error
162
+ end
163
+
164
+ it{ expect(thing.nearby.absolute_path).to match(%r"rainbow/a_place/a.file$")}
165
+ it{ expect(thing.nearby.absolute_path).to match(%r"^#{subject.absolute_path}") }
166
+
167
+ it{ expect(thing.certificate_file.absolute_path).to eq("/tmp/bundle_workdir/aws-creds/cert.pem")}
168
+ it{ expect(thing.bundle_manifest.absolute_path).to eq("/tmp/bundle_workdir/image.manifest.xml") }
169
+ it{ expect(thing.credentials_dir.absolute_path).to eq("/tmp/bundle_workdir/aws-creds") }
170
+ end
171
+ end
172
+
173
+ describe "multiple instances" do
174
+ class MultiSource
175
+ include Calibrate::Configurable
176
+
177
+ setting :one, 1
178
+ setting :nest, nested{
179
+ setting :two, 2
180
+ }
181
+ end
182
+
183
+ let :first do
184
+ MultiSource.new.setup_defaults
185
+ end
186
+
187
+ let :second do
188
+ MultiSource.new.setup_defaults
189
+ end
190
+
191
+ before :each do
192
+ first.one = "one"
193
+ first.nest.two = "two"
194
+ second
195
+ end
196
+
197
+ it "should not have any validation errors" do
198
+ expect do
199
+ first.check_required
200
+ second.check_required
201
+ end.not_to raise_error
202
+ end
203
+
204
+ it "should accurately reflect settings" do
205
+ expect(first.one).to eq("one")
206
+ expect(second.one).to eq(1)
207
+
208
+ expect(first.nest.two).to eq("two")
209
+ expect(second.nest.two).to eq(2)
210
+ end
211
+ end
212
+
213
+ describe "copying settings" do
214
+ class LeftStruct
215
+ include Calibrate::Configurable
216
+
217
+ setting(:normal, "1")
218
+ setting(:nested, nested{
219
+ setting :value, "2"
220
+ })
221
+ setting(:no_copy, 2).isnt(:copiable)
222
+ setting(:no_proxy, 3).isnt(:proxiable)
223
+ setting(:no_nothing, 4).isnt(:copiable).isnt(:proxiable)
224
+ setting(:not_on_target, 5)
225
+ end
226
+
227
+ class RightStruct
228
+ include Calibrate::Configurable
229
+
230
+ required_fields(:normal, :nested, :no_copy, :no_proxy, :no_nothing)
231
+ end
232
+
233
+ let :left do
234
+ LeftStruct.new.setup_defaults
235
+ end
236
+
237
+ let :right do
238
+ RightStruct.new.setup_defaults
239
+ end
240
+
241
+ it "should make copies not references" do
242
+ left.copy_settings_to(right)
243
+ expect(right.normal).to eq(left.normal)
244
+ expect(right.normal).to_not equal(left.normal)
245
+ expect(right.nested.value).to eq(left.nested.value)
246
+ expect(right.nested).to_not equal(left.nested)
247
+ expect(right.nested.value).to_not equal left.nested.value
248
+ end
249
+
250
+ it "should not copy no_copy" do
251
+ left.copy_settings_to(right)
252
+ expect(right.field_unset?(:normal)).to eq(false)
253
+ expect(right.normal).to eq("1")
254
+ expect(right.field_unset?(:no_copy)).to eq(true)
255
+ expect(right.field_unset?(:no_proxy)).to eq(false)
256
+ expect(right.no_proxy).to eq(3)
257
+ expect(right.field_unset?(:no_nothing)).to eq(true)
258
+ end
259
+
260
+ it "should not proxy no_proxy" do
261
+ left.proxy_settings.to(right)
262
+ expect(right.field_unset?(:normal)).to eq(false)
263
+ expect(right.normal).to eq("1")
264
+ expect(right.field_unset?(:no_copy)).to eq(false)
265
+ expect(right.no_copy).to eq(2)
266
+ expect(right.field_unset?(:no_proxy)).to eq(true)
267
+ expect(right.field_unset?(:no_nothing)).to eq(true)
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,17 @@
1
+ puts Dir::pwd
2
+ require 'test/unit'
3
+ begin
4
+ require 'spec'
5
+ rescue LoadError
6
+ false
7
+ end
8
+
9
+ class RSpecTest < Test::Unit::TestCase
10
+ def test_that_rspec_is_available
11
+ assert_nothing_raised("\n\n * RSpec isn't available - please run: gem install rspec *\n\n"){ ::Spec }
12
+ end
13
+
14
+ def test_that_specs_pass
15
+ assert(system(*%w{spec -f e -p **/*.rb spec}),"\n\n * Specs failed *\n\n")
16
+ end
17
+ end
File without changes
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: calibrate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Judson Lester
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ Add configurable settings to ruby object that can have defaults, be required, depend on one another.
15
+ email:
16
+ - nyarly@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/calibrate.rb
22
+ - lib/calibrate/configurable.rb
23
+ - lib/calibrate/configurable/class-methods.rb
24
+ - lib/calibrate/configurable/directory-structure.rb
25
+ - lib/calibrate/configurable/field-metadata.rb
26
+ - lib/calibrate/configurable/field-processor.rb
27
+ - lib/calibrate/configurable/instance-methods.rb
28
+ - lib/calibrate/configurable/proxy-value.rb
29
+ - lib/calibrate/yard-extensions.rb
30
+ - spec/configurable.rb
31
+ - spec_help/gem_test_suite.rb
32
+ - spec_help/spec_helper.rb
33
+ homepage: http://nyarly.github.com/calibrate
34
+ licenses:
35
+ - MIT
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options:
39
+ - "--inline-source"
40
+ - "--main"
41
+ - doc/README
42
+ - "--title"
43
+ - calibrate-0.0.1 Documentation
44
+ require_paths:
45
+ - lib/
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project: calibrate
58
+ rubygems_version: 2.4.8
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Configuration fields for Ruby objects
62
+ test_files:
63
+ - spec_help/gem_test_suite.rb