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