mattock 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,47 +1,6 @@
1
- module Mattock
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
- pid, status = Process.wait2(pid)
138
-
139
- stdout = consume_buffer(host_stdout)
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
@@ -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
- !decorated(verify_command).succeeds?
55
+ check_verification_command
52
56
  end
53
57
  end
54
58
  end
@@ -1,5 +1,5 @@
1
1
  module Mattock
2
- #Handles setting options on objects its mixed into
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'