calibrate 0.0.1

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