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