calibrate 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/calibrate.rb +1 -0
- data/lib/calibrate/configurable.rb +29 -0
- data/lib/calibrate/configurable/class-methods.rb +227 -0
- data/lib/calibrate/configurable/directory-structure.rb +163 -0
- data/lib/calibrate/configurable/field-metadata.rb +157 -0
- data/lib/calibrate/configurable/field-processor.rb +54 -0
- data/lib/calibrate/configurable/instance-methods.rb +90 -0
- data/lib/calibrate/configurable/proxy-value.rb +30 -0
- data/lib/calibrate/yard-extensions.rb +190 -0
- data/spec/configurable.rb +270 -0
- data/spec_help/gem_test_suite.rb +17 -0
- data/spec_help/spec_helper.rb +0 -0
- metadata +63 -0
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
|