mattock 0.4.1 → 0.5.0
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.
- data/lib/mattock/command-line.rb +16 -69
- data/lib/mattock/command-line/command-run-result.rb +131 -0
- data/lib/mattock/command-task.rb +5 -1
- data/lib/mattock/configurable.rb +8 -546
- data/lib/mattock/configurable/class-methods.rb +208 -0
- data/lib/mattock/configurable/directory-structure.rb +147 -0
- data/lib/mattock/configurable/field-metadata.rb +125 -0
- data/lib/mattock/configurable/field-processor.rb +54 -0
- data/lib/mattock/configurable/instance-methods.rb +75 -0
- data/lib/mattock/configurable/proxy-value.rb +30 -0
- data/lib/mattock/remote-command-task.rb +12 -11
- data/lib/mattock/task.rb +5 -0
- data/lib/mattock/tasklib.rb +22 -2
- data/lib/mattock/template-host.rb +3 -0
- data/lib/mattock/testing/mock-command-line.rb +1 -1
- data/spec/command-line.rb +1 -1
- data/spec/command-task.rb +4 -0
- data/spec/configurable.rb +22 -1
- data/spec/template-host.rb +1 -1
- data/spec_help/spec_helper.rb +7 -0
- metadata +29 -26
data/lib/mattock/command-line.rb
CHANGED
@@ -1,47 +1,6 @@
|
|
1
|
-
|
2
|
-
class CommandRunResult
|
3
|
-
def initialize(command, status, streams)
|
4
|
-
@command = command
|
5
|
-
@process_status = status
|
6
|
-
@streams = streams
|
7
|
-
end
|
8
|
-
attr_reader :process_status, :streams
|
9
|
-
|
10
|
-
def stdout
|
11
|
-
@streams[1]
|
12
|
-
end
|
13
|
-
|
14
|
-
def stderr
|
15
|
-
@streams[2]
|
16
|
-
end
|
17
|
-
|
18
|
-
def exit_code
|
19
|
-
@process_status.exitstatus
|
20
|
-
end
|
21
|
-
alias exit_status exit_code
|
22
|
-
|
23
|
-
def succeeded?
|
24
|
-
must_succeed!
|
25
|
-
return true
|
26
|
-
rescue
|
27
|
-
return false
|
28
|
-
end
|
29
|
-
|
30
|
-
def format_streams
|
31
|
-
"stdout:#{stdout.nil? || stdout.empty? ? "[empty]\n" : "\n#{stdout}"}" +
|
32
|
-
"stderr:#{stderr.nil? || stderr.empty? ? "[empty]\n" : "\n#{stderr}"}---"
|
33
|
-
end
|
34
|
-
|
35
|
-
def must_succeed!
|
36
|
-
case exit_code
|
37
|
-
when 0
|
38
|
-
return exit_code
|
39
|
-
else
|
40
|
-
fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{format_streams}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
1
|
+
require 'mattock/command-line/command-run-result'
|
44
2
|
|
3
|
+
module Mattock
|
45
4
|
class CommandLine
|
46
5
|
def self.define_chain_op(opname, klass)
|
47
6
|
define_method(opname) do |other|
|
@@ -76,6 +35,11 @@ module Mattock
|
|
76
35
|
|
77
36
|
alias_method :command_environment, :env
|
78
37
|
|
38
|
+
def set_env(name, value)
|
39
|
+
command_environment[name] = value
|
40
|
+
return self
|
41
|
+
end
|
42
|
+
|
79
43
|
def verbose
|
80
44
|
::Rake.verbose && ::Rake.verbose != ::Rake::FileUtilsExt::DEFAULT
|
81
45
|
end
|
@@ -122,6 +86,12 @@ module Mattock
|
|
122
86
|
redirect_from(path, 0)
|
123
87
|
end
|
124
88
|
|
89
|
+
def replace_us
|
90
|
+
puts "Ceding execution to: "
|
91
|
+
puts string_format
|
92
|
+
Process.exec(command_environment, command)
|
93
|
+
end
|
94
|
+
|
125
95
|
def spawn_process
|
126
96
|
host_stdout, cmd_stdout = IO.pipe
|
127
97
|
host_stderr, cmd_stderr = IO.pipe
|
@@ -134,35 +104,12 @@ module Mattock
|
|
134
104
|
end
|
135
105
|
|
136
106
|
def collect_result(pid, host_stdout, host_stderr)
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
stderr = consume_buffer(host_stderr)
|
141
|
-
result = CommandRunResult.new(command, status, {1 => stdout, 2 => stderr})
|
142
|
-
host_stdout.close
|
143
|
-
host_stderr.close
|
144
|
-
|
107
|
+
result = CommandRunResult.new(pid, self)
|
108
|
+
result.streams = {1 => host_stdout, 2 => host_stderr}
|
109
|
+
result.wait
|
145
110
|
return result
|
146
111
|
end
|
147
112
|
|
148
|
-
#Gets all the data out of buffer, even if somehow it doesn't have an EOF
|
149
|
-
#Escpecially useful for programs (e.g. ssh) that sometime set their stderr
|
150
|
-
#to O_NONBLOCK
|
151
|
-
def consume_buffer(io)
|
152
|
-
accumulate = []
|
153
|
-
waits = 3
|
154
|
-
begin
|
155
|
-
while chunk = io.read_nonblock(4096)
|
156
|
-
accumulate << chunk
|
157
|
-
end
|
158
|
-
rescue IO::WaitReadable => ex
|
159
|
-
retry if (waits -= 1) > 0
|
160
|
-
end
|
161
|
-
return accumulate.join
|
162
|
-
rescue EOFError
|
163
|
-
return accumulate.join
|
164
|
-
end
|
165
|
-
|
166
113
|
#If I wasn't worried about writing my own limited shell, I'd say e.g.
|
167
114
|
#Pipeline would be an explicit chain of pipes... which is probably as
|
168
115
|
#originally intended :/
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Mattock
|
2
|
+
class CommandLine
|
3
|
+
class CommandRunResult
|
4
|
+
def initialize(pid, command)
|
5
|
+
@command = command
|
6
|
+
@pid = pid
|
7
|
+
|
8
|
+
#####
|
9
|
+
@process_status = nil
|
10
|
+
@streams = {}
|
11
|
+
@consume_timeout = nil
|
12
|
+
end
|
13
|
+
attr_reader :process_status, :pid
|
14
|
+
attr_accessor :consume_timeout, :streams
|
15
|
+
|
16
|
+
def stdout
|
17
|
+
@streams[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def stderr
|
21
|
+
@streams[2]
|
22
|
+
end
|
23
|
+
|
24
|
+
def exit_code
|
25
|
+
@process_status.exitstatus
|
26
|
+
end
|
27
|
+
alias exit_status exit_code
|
28
|
+
|
29
|
+
def succeeded?
|
30
|
+
must_succeed!
|
31
|
+
return true
|
32
|
+
rescue
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
|
36
|
+
def format_streams
|
37
|
+
"stdout:#{stdout.nil? || stdout.empty? ? "[empty]\n" : "\n#{stdout}"}" +
|
38
|
+
"stderr:#{stderr.nil? || stderr.empty? ? "[empty]\n" : "\n#{stderr}"}---"
|
39
|
+
end
|
40
|
+
|
41
|
+
def must_succeed!
|
42
|
+
case exit_code
|
43
|
+
when 0
|
44
|
+
return exit_code
|
45
|
+
else
|
46
|
+
fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{format_streams}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def wait
|
51
|
+
@accumulators = {}
|
52
|
+
waits = {}
|
53
|
+
@buffered_echo = []
|
54
|
+
|
55
|
+
ioes = streams.values
|
56
|
+
ioes.each do |io|
|
57
|
+
@accumulators[io] = []
|
58
|
+
waits[io] = 3
|
59
|
+
end
|
60
|
+
begin_echoing = Time.now + (@consume_timeout || 3)
|
61
|
+
|
62
|
+
@live_ioes = ioes.dup
|
63
|
+
|
64
|
+
until @live_ioes.empty? do
|
65
|
+
newpid, @process_status = Process.waitpid2(pid, Process::WNOHANG)
|
66
|
+
|
67
|
+
unless @process_status.nil?
|
68
|
+
consume_buffers(@live_ioes)
|
69
|
+
break
|
70
|
+
end
|
71
|
+
|
72
|
+
timeout = 0
|
73
|
+
|
74
|
+
if !@buffered_echo.nil?
|
75
|
+
timeout = begin_echoing - Time.now
|
76
|
+
if timeout < 0
|
77
|
+
puts
|
78
|
+
puts "Long running command output:"
|
79
|
+
puts @buffered_echo.join
|
80
|
+
@buffered_echo = nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if timeout > 0
|
85
|
+
result = IO::select(@live_ioes, [], @live_ioes, timeout)
|
86
|
+
else
|
87
|
+
result = IO::select(@live_ioes, [], @live_ioes, 1)
|
88
|
+
end
|
89
|
+
|
90
|
+
unless result.nil? #timeout
|
91
|
+
readable, _writeable, errored = *result
|
92
|
+
unless errored.empty?
|
93
|
+
raise "Error on IO: #{errored.inspect}"
|
94
|
+
end
|
95
|
+
|
96
|
+
consume_buffers(readable)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if @process_status.nil?
|
101
|
+
newpid, @process_status = Process.waitpid2(pid)
|
102
|
+
end
|
103
|
+
|
104
|
+
ioes.each do |io|
|
105
|
+
io.close
|
106
|
+
end
|
107
|
+
@streams = Hash[ioes.each_with_index.map{|io, index| [index + 1, @accumulators[io].join]}]
|
108
|
+
end
|
109
|
+
|
110
|
+
def consume_buffers(readable)
|
111
|
+
if not(readable.nil? or readable.empty?)
|
112
|
+
readable.each do |io|
|
113
|
+
begin
|
114
|
+
while chunk = io.read_nonblock(4096)
|
115
|
+
if @buffered_echo.nil?
|
116
|
+
puts chunk
|
117
|
+
else
|
118
|
+
@buffered_echo << chunk
|
119
|
+
end
|
120
|
+
@accumulators[io] << chunk
|
121
|
+
end
|
122
|
+
rescue IO::WaitReadable => ex
|
123
|
+
rescue EOFError => ex
|
124
|
+
@live_ioes.delete(io)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/mattock/command-task.rb
CHANGED
@@ -43,12 +43,16 @@ module Mattock
|
|
43
43
|
decorated(command).must_succeed!
|
44
44
|
end
|
45
45
|
|
46
|
+
def check_verification_command
|
47
|
+
!decorated(verify_command).succeeds?
|
48
|
+
end
|
49
|
+
|
46
50
|
def needed?
|
47
51
|
finalize_configuration
|
48
52
|
if verify_command.nil?
|
49
53
|
super
|
50
54
|
else
|
51
|
-
|
55
|
+
check_verification_command
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
data/lib/mattock/configurable.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Mattock
|
2
|
-
#Handles setting options on objects
|
2
|
+
#Handles setting options on objects it's mixed into
|
3
3
|
#
|
4
4
|
#Settings can have default values or be required (as opposed to defaulting to
|
5
5
|
#nil). Settings and their defaults are inherited (and can be overridden) by
|
@@ -18,550 +18,12 @@ module Mattock
|
|
18
18
|
super("No default value for field #{field_name} on class #{klass.name}")
|
19
19
|
end
|
20
20
|
end
|
21
|
-
|
22
|
-
class FieldMetadata
|
23
|
-
attr_accessor :name, :default_value
|
24
|
-
|
25
|
-
DEFAULT_PROPERTIES = {
|
26
|
-
:copiable => true,
|
27
|
-
:proxiable => true,
|
28
|
-
:required => false,
|
29
|
-
:runtime => false,
|
30
|
-
:defaulting => true,
|
31
|
-
}
|
32
|
-
def initialize(name, value)
|
33
|
-
@name = name
|
34
|
-
@default_value = value
|
35
|
-
@properties = DEFAULT_PROPERTIES.clone
|
36
|
-
end
|
37
|
-
|
38
|
-
def inspect
|
39
|
-
set_props = DEFAULT_PROPERTIES.keys.find_all do |prop|
|
40
|
-
@properties[prop]
|
41
|
-
end
|
42
|
-
"Field: #{name}: #{default_value.inspect} #{set_props.inspect}"
|
43
|
-
end
|
44
|
-
|
45
|
-
def validate_property_name(name)
|
46
|
-
unless DEFAULT_PROPERTIES.has_key?(name)
|
47
|
-
raise "Invalid field property #{name.inspect} - valid are: #{DEFAULT_PROPERTIES.keys.inspect}"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def is?(property)
|
52
|
-
validate_property_name(property)
|
53
|
-
@properties[property]
|
54
|
-
end
|
55
|
-
|
56
|
-
def is_not?(property)
|
57
|
-
validate_property_name(property)
|
58
|
-
!@properties[property]
|
59
|
-
end
|
60
|
-
alias isnt? is_not?
|
61
|
-
|
62
|
-
def is(property)
|
63
|
-
validate_property_name(property)
|
64
|
-
@properties[property] = true
|
65
|
-
self
|
66
|
-
end
|
67
|
-
|
68
|
-
def is_not(property)
|
69
|
-
validate_property_name(property)
|
70
|
-
@properties[property] = false
|
71
|
-
self
|
72
|
-
end
|
73
|
-
alias isnt is_not
|
74
|
-
|
75
|
-
def ivar_name
|
76
|
-
"@#{name}"
|
77
|
-
end
|
78
|
-
|
79
|
-
def writer_method
|
80
|
-
"#{name}="
|
81
|
-
end
|
82
|
-
|
83
|
-
def reader_method
|
84
|
-
name
|
85
|
-
end
|
86
|
-
|
87
|
-
def immediate_value_on(instance)
|
88
|
-
instance.instance_variable_get(ivar_name)
|
89
|
-
end
|
90
|
-
|
91
|
-
def value_on(instance)
|
92
|
-
value = immediate_value_on(instance)
|
93
|
-
if ProxyValue === value
|
94
|
-
value.field.value_on(value.source)
|
95
|
-
else
|
96
|
-
value
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def set_on?(instance)
|
101
|
-
return false unless instance.instance_variable_defined?(ivar_name)
|
102
|
-
value = immediate_value_on(instance)
|
103
|
-
if name == :destination_path
|
104
|
-
end
|
105
|
-
if ProxyValue === value
|
106
|
-
value.field.set_on?(value.source)
|
107
|
-
else
|
108
|
-
true
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def unset_on?(instance)
|
113
|
-
!set_on?(instance)
|
114
|
-
end
|
115
|
-
|
116
|
-
def missing_on?(instance)
|
117
|
-
return false unless is?(:required)
|
118
|
-
if instance.respond_to?(:runtime?) and !instance.runtime?
|
119
|
-
return runtime_missing_on?(instance)
|
120
|
-
else
|
121
|
-
return !set_on?(instance)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def runtime_missing_on?(instance)
|
126
|
-
return false if is?(:runtime)
|
127
|
-
return true unless instance.instance_variable_defined?(ivar_name)
|
128
|
-
value = immediate_value_on(instance)
|
129
|
-
if ProxyValue === value
|
130
|
-
value.field.runtime_missing_on?(value.source)
|
131
|
-
else
|
132
|
-
false
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
class ProxyValue
|
138
|
-
def initialize(source, field)
|
139
|
-
@source, @field = source, field
|
140
|
-
end
|
141
|
-
attr_reader :source, :field
|
142
|
-
|
143
|
-
def inspect
|
144
|
-
"#{self.class.name.split(':').last}: #{source.class.name}.#{field.inspect}"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
class ProxyDecorator
|
149
|
-
def initialize(configurable)
|
150
|
-
@configurable = configurable
|
151
|
-
end
|
152
|
-
|
153
|
-
def method_missing(name, *args, &block)
|
154
|
-
super unless block.nil? and args.empty?
|
155
|
-
super unless @configurable.respond_to?(name)
|
156
|
-
return ProxyValue.new(@configurable, @configurable.class.field_metadata(name))
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
class FieldProcessor
|
161
|
-
def initialize(source)
|
162
|
-
@source = source
|
163
|
-
@field_names = filter(source.class.field_names)
|
164
|
-
end
|
165
|
-
attr_accessor :field_names
|
166
|
-
attr_reader :source
|
167
|
-
|
168
|
-
def filter_attribute
|
169
|
-
raise NotImplementedError
|
170
|
-
end
|
171
|
-
|
172
|
-
def filter(field_names)
|
173
|
-
field_names.find_all do |name|
|
174
|
-
source.class.field_metadata(name).is?(filter_attribute)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def value(field)
|
179
|
-
source.__send__(field.reader_method)
|
180
|
-
end
|
181
|
-
|
182
|
-
def to(target)
|
183
|
-
field_names.each do |name|
|
184
|
-
field = source.class.field_metadata(name)
|
185
|
-
next unless target.respond_to?(field.writer_method)
|
186
|
-
target.__send__(field.writer_method, value(field))
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
class SettingsCopier < FieldProcessor
|
192
|
-
def filter_attribute
|
193
|
-
:copiable
|
194
|
-
end
|
195
|
-
|
196
|
-
def value(field)
|
197
|
-
field.immediate_value_on(source)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
class SettingsProxier < FieldProcessor
|
202
|
-
def filter_attribute
|
203
|
-
:proxiable
|
204
|
-
end
|
205
|
-
|
206
|
-
def value(field)
|
207
|
-
ProxyValue.new(source, field)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
#Describes class level DSL & machinery for working with configuration
|
212
|
-
#managment.
|
213
|
-
#
|
214
|
-
#@example
|
215
|
-
# class ConfExample
|
216
|
-
# include Configurable
|
217
|
-
#
|
218
|
-
# setting :foo
|
219
|
-
# settings :bar => 1, :baz => 3
|
220
|
-
# nil_fields :hoo, :ha, :harum
|
221
|
-
# required_fields :must
|
222
|
-
#
|
223
|
-
# def initialize
|
224
|
-
# setup_defaults
|
225
|
-
# end
|
226
|
-
# end
|
227
|
-
#
|
228
|
-
# ce = ConfExample.new
|
229
|
-
# ce.bar #=> 1
|
230
|
-
# ce.hoo #=> nil
|
231
|
-
# ce.hoo = "hallo"
|
232
|
-
# ce.check_required #=> raises error because :must and :foo aren't set
|
233
|
-
module ClassMethods
|
234
|
-
def inspect_instance(instance, indent="")
|
235
|
-
field_names.map do |name|
|
236
|
-
meta = field_metadata(name)
|
237
|
-
"#{indent}#{meta.inspect} => #{meta.immediate_value_on(instance).inspect}(#{meta.value_on(instance).inspect})"
|
238
|
-
end.join("\n")
|
239
|
-
end
|
240
|
-
|
241
|
-
def default_values
|
242
|
-
@default_values ||= []
|
243
|
-
end
|
244
|
-
|
245
|
-
def field_names
|
246
|
-
names = default_values.map{|field| field.name}
|
247
|
-
if Configurable > superclass
|
248
|
-
names | superclass.field_names
|
249
|
-
else
|
250
|
-
names
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
def field_metadata(name)
|
255
|
-
field = default_values.find{|field| field.name == name}
|
256
|
-
if field.nil? and Configurable > superclass
|
257
|
-
superclass.field_metadata(name)
|
258
|
-
else
|
259
|
-
field
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
#@raises NoDefaultValue
|
264
|
-
def default_value_for(name)
|
265
|
-
field = field_metadata(name)
|
266
|
-
raise NoDefaultValue.new(name,self) unless field.is?(:defaulting)
|
267
|
-
return field.default_value
|
268
|
-
end
|
269
|
-
|
270
|
-
#Creates an anonymous Configurable - useful in complex setups for nested
|
271
|
-
#settings
|
272
|
-
#@example SSH options
|
273
|
-
# setting :ssh => nested(:username => "me", :password => nil)
|
274
|
-
def nested(hash=nil, &block)
|
275
|
-
nested = Class.new(Struct)
|
276
|
-
nested.settings(hash || {})
|
277
|
-
if block_given?
|
278
|
-
nested.instance_eval(&block)
|
279
|
-
end
|
280
|
-
return nested
|
281
|
-
end
|
282
|
-
|
283
|
-
#Quick list of setting fields with a default value of nil. Useful
|
284
|
-
#especially with {CascadingDefinition#resolve_configuration}
|
285
|
-
def nil_fields(*names)
|
286
|
-
names.each do |name|
|
287
|
-
setting(name, nil)
|
288
|
-
end
|
289
|
-
self
|
290
|
-
end
|
291
|
-
alias nil_field nil_fields
|
292
|
-
|
293
|
-
#List fields with no default for with a value must be set before
|
294
|
-
#definition.
|
295
|
-
def required_fields(*names)
|
296
|
-
names.each do |name|
|
297
|
-
setting(name)
|
298
|
-
end
|
299
|
-
self
|
300
|
-
end
|
301
|
-
alias required_field required_fields
|
302
|
-
|
303
|
-
RequiredField = Object.new.freeze
|
304
|
-
|
305
|
-
#Defines a setting on this class - much like a attr_accessible call, but
|
306
|
-
#allows for defaults and required settings
|
307
|
-
def setting(name, default_value = RequiredField)
|
308
|
-
name = name.to_sym
|
309
|
-
metadata =
|
310
|
-
if default_value == RequiredField
|
311
|
-
FieldMetadata.new(name, nil).is(:required).isnt(:defaulting)
|
312
|
-
else
|
313
|
-
FieldMetadata.new(name, default_value)
|
314
|
-
end
|
315
|
-
|
316
|
-
attr_writer(name)
|
317
|
-
define_method(metadata.reader_method) do
|
318
|
-
value = metadata.value_on(self)
|
319
|
-
end
|
320
|
-
|
321
|
-
if existing = default_values.find{|field| field.name == name} and existing.default_value != default_value
|
322
|
-
source_line = caller.drop_while{|line| /#{__FILE__}/ =~ line}.first
|
323
|
-
warn "Changing default value of #{self.name}##{name} from #{existing.default_value.inspect} to #{default_value.inspect}"
|
324
|
-
" (at: #{source_line})"
|
325
|
-
end
|
326
|
-
default_values << metadata
|
327
|
-
metadata
|
328
|
-
end
|
329
|
-
|
330
|
-
def runtime_required_fields(*names)
|
331
|
-
names.each do |name|
|
332
|
-
runtime_setting(name)
|
333
|
-
end
|
334
|
-
self
|
335
|
-
end
|
336
|
-
alias runtime_required_field runtime_required_fields
|
337
|
-
|
338
|
-
def runtime_setting(name, default_value = RequiredField)
|
339
|
-
setting(name, default_value).is(:runtime)
|
340
|
-
end
|
341
|
-
|
342
|
-
#@param [Hash] hash Pairs of name/value to be converted into
|
343
|
-
# setting/default
|
344
|
-
def settings(hash)
|
345
|
-
hash.each_pair do |name, value|
|
346
|
-
setting(name, value)
|
347
|
-
end
|
348
|
-
return self
|
349
|
-
end
|
350
|
-
alias runtime_settings settings
|
351
|
-
|
352
|
-
def set_defaults_on(instance)
|
353
|
-
if Configurable > superclass
|
354
|
-
superclass.set_defaults_on(instance)
|
355
|
-
end
|
356
|
-
default_values.each do |field|
|
357
|
-
next unless field.is? :defaulting
|
358
|
-
value = field.default_value
|
359
|
-
if Module === value and Configurable > value
|
360
|
-
value = value.new
|
361
|
-
value.class.set_defaults_on(value)
|
362
|
-
end
|
363
|
-
instance.__send__(field.writer_method, value)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
def missing_required_fields_on(instance)
|
368
|
-
missing = []
|
369
|
-
if Configurable > superclass
|
370
|
-
missing = superclass.missing_required_fields_on(instance)
|
371
|
-
end
|
372
|
-
default_values.each do |field|
|
373
|
-
if field.missing_on?(instance)
|
374
|
-
missing << field.name
|
375
|
-
else
|
376
|
-
set_value = instance.__send__(field.reader_method)
|
377
|
-
if Configurable === set_value
|
378
|
-
missing += set_value.class.missing_required_fields_on(set_value).map do |field|
|
379
|
-
[name, field].join(".")
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
384
|
-
return missing
|
385
|
-
end
|
386
|
-
|
387
|
-
def copy_settings(from, to, &block)
|
388
|
-
if Configurable > superclass
|
389
|
-
superclass.copy_settings(from, to, &block)
|
390
|
-
end
|
391
|
-
default_values.each do |field|
|
392
|
-
begin
|
393
|
-
value =
|
394
|
-
if block_given?
|
395
|
-
yield(from, field)
|
396
|
-
else
|
397
|
-
from.__send__(field.reader_method)
|
398
|
-
end
|
399
|
-
if Configurable === value
|
400
|
-
value = value.clone
|
401
|
-
end
|
402
|
-
to.__send__(field.writer_method, value)
|
403
|
-
rescue NoMethodError
|
404
|
-
#shrug it off
|
405
|
-
end
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
def to_hash(obj)
|
410
|
-
hash = if Configurable > superclass
|
411
|
-
superclass.to_hash(obj)
|
412
|
-
else
|
413
|
-
{}
|
414
|
-
end
|
415
|
-
hash.merge( Hash[default_values.map{|field|
|
416
|
-
begin
|
417
|
-
value = obj.__send__(field.reader_method)
|
418
|
-
value =
|
419
|
-
case value
|
420
|
-
when Configurable
|
421
|
-
value.to_hash
|
422
|
-
else
|
423
|
-
value
|
424
|
-
end
|
425
|
-
[field.name, value]
|
426
|
-
rescue NoMethodError
|
427
|
-
end
|
428
|
-
}])
|
429
|
-
end
|
430
|
-
|
431
|
-
|
432
|
-
def included(mod)
|
433
|
-
mod.extend ClassMethods
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
extend ClassMethods
|
438
|
-
|
439
|
-
module DirectoryStructure
|
440
|
-
module ClassMethods
|
441
|
-
RequiredField = ::Mattock::Configurable::ClassMethods::RequiredField
|
442
|
-
|
443
|
-
attr_accessor :path_heirarchy
|
444
|
-
attr_accessor :path_fields
|
445
|
-
|
446
|
-
def dir(field_name, *args)
|
447
|
-
rel_path = RequiredField
|
448
|
-
if String === args.first
|
449
|
-
rel_path = args.shift
|
450
|
-
end
|
451
|
-
parent_field = path(field_name, rel_path)
|
452
|
-
self.path_heirarchy += args.map do |child_field|
|
453
|
-
[parent_field, child_field]
|
454
|
-
end
|
455
|
-
return parent_field
|
456
|
-
end
|
457
|
-
|
458
|
-
def path(field_name, rel_path=RequiredField)
|
459
|
-
field = setting(field_name, nested{
|
460
|
-
required_field :absolute_path
|
461
|
-
setting :relative_path, rel_path
|
462
|
-
})
|
463
|
-
path_fields << field
|
464
|
-
return field
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
def self.included(sub)
|
469
|
-
sub.extend ClassMethods
|
470
|
-
sub.path_heirarchy = []
|
471
|
-
sub.path_fields = []
|
472
|
-
end
|
473
|
-
|
474
|
-
def resolve_paths
|
475
|
-
missing_relatives = []
|
476
|
-
self.class.path_heirarchy.reverse.each do |parent_field, child_field|
|
477
|
-
child = child_field.value_on(self)
|
478
|
-
next unless child.field_unset?(:absolute_path)
|
479
|
-
if child.field_unset?(:relative_path)
|
480
|
-
missing_relatives << child_field
|
481
|
-
next
|
482
|
-
end
|
483
|
-
parent = parent_field.value_on(self)
|
484
|
-
child.absolute_path = File::join(parent.absolute_path, child.relative_path)
|
485
|
-
end
|
486
|
-
unless missing_relatives.empty?
|
487
|
-
raise "Required field#{missing_relatives.length == 1 ? "" : "s"} #{missing_relatives.map{|field| "#{field.name}.relative_path".inspect}.join(", ")} unset on #{self.inspect}"
|
488
|
-
end
|
489
|
-
self.class.path_fields.each do |field|
|
490
|
-
value = field.value_on(self)
|
491
|
-
next unless value.field_unset?(:relative_path)
|
492
|
-
value.relative_path = value.absolute_path
|
493
|
-
end
|
494
|
-
end
|
495
|
-
end
|
496
|
-
|
497
|
-
def copy_settings
|
498
|
-
SettingsCopier.new(self)
|
499
|
-
end
|
500
|
-
|
501
|
-
def copy_settings_to(other)
|
502
|
-
copy_settings.to(other)
|
503
|
-
self
|
504
|
-
end
|
505
|
-
|
506
|
-
def proxy_settings
|
507
|
-
SettingsProxier.new(self)
|
508
|
-
end
|
509
|
-
|
510
|
-
def proxy_settings_to(other)
|
511
|
-
proxy_settings.to(other)
|
512
|
-
end
|
513
|
-
|
514
|
-
def to_hash
|
515
|
-
self.class.to_hash(self)
|
516
|
-
end
|
517
|
-
|
518
|
-
def unset_defaults_guard
|
519
|
-
raise "Tried to check required settings before running setup_defaults"
|
520
|
-
end
|
521
|
-
|
522
|
-
#Call during initialize to set default values on settings - if you're using
|
523
|
-
#Configurable outside of Mattock, be sure this gets called.
|
524
|
-
def setup_defaults
|
525
|
-
def self.unset_defaults_guard
|
526
|
-
end
|
527
|
-
|
528
|
-
self.class.set_defaults_on(self)
|
529
|
-
self
|
530
|
-
end
|
531
|
-
|
532
|
-
#Checks that all required fields have be set, otherwise raises an error
|
533
|
-
#@raise RuntimeError if any required fields are unset
|
534
|
-
def check_required
|
535
|
-
unset_defaults_guard
|
536
|
-
missing = self.class.missing_required_fields_on(self)
|
537
|
-
unless missing.empty?
|
538
|
-
raise "Required field#{missing.length > 1 ? "s" : ""} #{missing.map{|field| field.to_s.inspect}.join(", ")} unset on #{self.inspect}"
|
539
|
-
end
|
540
|
-
self
|
541
|
-
end
|
542
|
-
|
543
|
-
def proxy_value
|
544
|
-
ProxyDecorator.new(self)
|
545
|
-
end
|
546
|
-
|
547
|
-
#XXX deprecate
|
548
|
-
def unset?(value)
|
549
|
-
value.nil?
|
550
|
-
end
|
551
|
-
|
552
|
-
def field_unset?(name)
|
553
|
-
self.class.field_metadata(name).unset_on?(self)
|
554
|
-
end
|
555
|
-
|
556
|
-
def fail_unless_set(name)
|
557
|
-
if self.class.field_metadata(name).unset_on?(self)
|
558
|
-
raise "Assertion failed: Field #{name} unset"
|
559
|
-
end
|
560
|
-
true
|
561
|
-
end
|
562
|
-
|
563
|
-
class Struct
|
564
|
-
include Configurable
|
565
|
-
end
|
566
21
|
end
|
567
22
|
end
|
23
|
+
|
24
|
+
require 'mattock/configurable/field-metadata'
|
25
|
+
require 'mattock/configurable/proxy-value'
|
26
|
+
require 'mattock/configurable/field-processor'
|
27
|
+
require 'mattock/configurable/class-methods'
|
28
|
+
require 'mattock/configurable/instance-methods'
|
29
|
+
require 'mattock/configurable/directory-structure'
|