mattock 0.2.13 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,6 +24,7 @@ module Mattock
24
24
  include Configurable
25
25
 
26
26
  def initialize(*tasklibs)
27
+ @runtime = false
27
28
  setup_defaults
28
29
  default_configuration(*tasklibs)
29
30
 
@@ -35,6 +36,7 @@ module Mattock
35
36
  define
36
37
  end
37
38
 
39
+
38
40
  #@param [TaskLib] tasklibs Other libs upon which this one depends to set
39
41
  # its defaults
40
42
  #Sets default values for library settings
@@ -60,9 +62,42 @@ module Mattock
60
62
  check_required
61
63
  end
62
64
 
65
+
63
66
  #Any useful TaskLib will override this to define tasks, essentially like a
64
67
  #templated Rakefile snippet.
65
68
  def define
66
69
  end
67
70
  end
71
+
72
+ module DeferredDefinition
73
+ def self.add_settings(mod)
74
+ mod.setting(:configuration_block, proc{})
75
+ end
76
+
77
+ def initialize(*args, &block)
78
+ @runtime = false
79
+ @finalized = false
80
+ super
81
+ end
82
+
83
+ def runtime_definition(&block)
84
+ self.configuration_block = block
85
+ end
86
+
87
+ def runtime?
88
+ !!@runtime
89
+ end
90
+
91
+ def resolve_runtime_configuration
92
+ end
93
+
94
+ def finalize_configuration
95
+ return if @finalized
96
+ @runtime = true
97
+ configuration_block[self]
98
+ resolve_runtime_configuration
99
+ confirm_configuration
100
+ @finalized = true
101
+ end
102
+ end
68
103
  end
@@ -32,12 +32,32 @@ module Mattock
32
32
  when 0
33
33
  return exit_code
34
34
  else
35
- fail "Command #{@command.inspect} failed with exit status #{$?.exitstatus}: \n#{streams.inspect}"
35
+ fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{streams.inspect}"
36
36
  end
37
37
  end
38
38
  end
39
39
 
40
40
  class CommandLine
41
+ def self.define_chain_op(opname, klass)
42
+ define_method(opname) do |other|
43
+ unless CommandLine === other
44
+ other = CommandLine.new(*[*other])
45
+ end
46
+ chain = nil
47
+ if klass === self
48
+ chain = self
49
+ else
50
+ chain = klass.new
51
+ chain.add(self)
52
+ end
53
+ chain.add(other)
54
+ end
55
+ end
56
+
57
+ def self.define_op(opname)
58
+ CommandLine.define_chain_op(opname, self)
59
+ end
60
+
41
61
  def initialize(executable, *options)
42
62
  @executable = executable
43
63
  @options = options
@@ -56,7 +76,11 @@ module Mattock
56
76
  end
57
77
 
58
78
  def command
59
- ([executable] + options + @redirections).join(" ")
79
+ ([executable] + options_composition + @redirections).join(" ")
80
+ end
81
+
82
+ def options_composition
83
+ options
60
84
  end
61
85
 
62
86
  def redirect_to(stream, path)
@@ -110,6 +134,12 @@ module Mattock
110
134
  end
111
135
  end
112
136
 
137
+ module CommandLineDSL
138
+ def cmd(*args)
139
+ CommandLine.new(*args)
140
+ end
141
+ end
142
+
113
143
  class ShellEscaped < CommandLine
114
144
  def initialize(cmd)
115
145
  @escaped = cmd
@@ -135,6 +165,7 @@ module Mattock
135
165
  def add(cmd)
136
166
  yield cmd if block_given?
137
167
  @commands << cmd
168
+ self
138
169
  end
139
170
 
140
171
  def name
@@ -143,18 +174,24 @@ module Mattock
143
174
  end
144
175
 
145
176
  class WrappingChain < CommandChain
177
+ define_op('-')
178
+
146
179
  def command
147
180
  @commands.map{|cmd| cmd.command}.join(" -- ")
148
181
  end
149
182
  end
150
183
 
151
184
  class PrereqChain < CommandChain
185
+ define_op('&')
186
+
152
187
  def command
153
188
  @commands.map{|cmd| cmd.command}.join(" && ")
154
189
  end
155
190
  end
156
191
 
157
192
  class PipelineChain < CommandChain
193
+ define_op('|')
194
+
158
195
  def command
159
196
  @commands.map{|cmd| cmd.command}.join(" | ")
160
197
  end
@@ -3,9 +3,12 @@ require 'mattock/command-line'
3
3
 
4
4
  module Mattock
5
5
  class CommandTask < Task
6
+ include CommandLineDSL
7
+ extend CommandLineDSL
8
+
6
9
  setting(:task_name, :run)
7
- setting(:command)
8
- setting(:verify_command, nil)
10
+ runtime_setting(:verify_command, nil)
11
+ runtime_setting(:command)
9
12
 
10
13
  def verify_command
11
14
  if @verify_command.respond_to?(:call)
@@ -14,8 +17,8 @@ module Mattock
14
17
  @verify_command
15
18
  end
16
19
 
17
- def decorated(cmd)
18
- cmd
20
+ def decorated(command)
21
+ command
19
22
  end
20
23
 
21
24
  def action
@@ -23,6 +26,7 @@ module Mattock
23
26
  end
24
27
 
25
28
  def needed?
29
+ finalize_configuration
26
30
  unless verify_command.nil?
27
31
  !decorated(verify_command).succeeds?
28
32
  else
@@ -10,17 +10,194 @@ module Mattock
10
10
  #
11
11
  #@example (see ClassMethods)
12
12
  module Configurable
13
- RequiredField = Object.new
14
- class << RequiredField
15
- def to_s
16
- "<unset>"
13
+ class FieldMetadata
14
+ attr_accessor :name, :default_value
15
+
16
+ DEFAULT_PROPERTIES = {
17
+ :copiable => true,
18
+ :proxiable => true,
19
+ :required => false,
20
+ :runtime => false,
21
+ :defaulting => true,
22
+ }
23
+ def initialize(name, value)
24
+ @name = name
25
+ @default_value = value
26
+ @properties = DEFAULT_PROPERTIES.clone
27
+ end
28
+
29
+ def inspect
30
+ set_props = DEFAULT_PROPERTIES.keys.find_all do |prop|
31
+ @properties[prop]
32
+ end
33
+ "Field: #{name}: #{default_value.inspect} #{set_props.inspect}"
34
+ end
35
+
36
+ def validate_property_name(name)
37
+ unless DEFAULT_PROPERTIES.has_key?(name)
38
+ raise "Invalid field property #{name.inspect} - valid are: #{DEFAULT_PROPERTIES.keys.inspect}"
39
+ end
40
+ end
41
+
42
+ def is?(property)
43
+ validate_property_name(property)
44
+ @properties[property]
45
+ end
46
+
47
+ def is_not?(property)
48
+ validate_property_name(property)
49
+ !@properties[property]
50
+ end
51
+ alias isnt? is_not?
52
+
53
+ def is(property)
54
+ validate_property_name(property)
55
+ @properties[property] = true
56
+ self
57
+ end
58
+
59
+ def is_not(property)
60
+ validate_property_name(property)
61
+ @properties[property] = false
62
+ self
63
+ end
64
+ alias isnt is_not
65
+
66
+ def ivar_name
67
+ "@#{name}"
68
+ end
69
+
70
+ def writer_method
71
+ "#{name}="
72
+ end
73
+
74
+ def reader_method
75
+ name
17
76
  end
18
77
 
78
+ def immediate_value_on(instance)
79
+ instance.instance_variable_get(ivar_name)
80
+ end
81
+
82
+ def value_on(instance)
83
+ value = immediate_value_on(instance)
84
+ if ProxyValue === value
85
+ value.field.value_on(value.source)
86
+ else
87
+ value
88
+ end
89
+ end
90
+
91
+ def set_on?(instance)
92
+ return false unless instance.instance_variable_defined?(ivar_name)
93
+ value = immediate_value_on(instance)
94
+ if name == :destination_path
95
+ end
96
+ if ProxyValue === value
97
+ value.field.set_on?(value.source)
98
+ else
99
+ true
100
+ end
101
+ end
102
+
103
+ def unset_on?(instance)
104
+ !set_on?(instance)
105
+ end
106
+
107
+ def missing_on?(instance)
108
+ return false unless is?(:required)
109
+ if instance.respond_to?(:runtime?) and !instance.runtime?
110
+ return runtime_missing_on?(instance)
111
+ else
112
+ return !set_on?(instance)
113
+ end
114
+ end
115
+
116
+ def runtime_missing_on?(instance)
117
+ return false if is?(:runtime)
118
+ return true unless instance.instance_variable_defined?(ivar_name)
119
+ value = immediate_value_on(instance)
120
+ if ProxyValue === value
121
+ value.field.runtime_missing_on?(value.source)
122
+ else
123
+ false
124
+ end
125
+ end
126
+ end
127
+
128
+ class ProxyValue
129
+ def initialize(source, field)
130
+ @source, @field = source, field
131
+ end
132
+ attr_reader :source, :field
133
+
19
134
  def inspect
20
- to_s
135
+ "#{self.class.name.split(':').last}: #{value}"
136
+ end
137
+ end
138
+
139
+ class ProxyDecorator
140
+ def initialize(configurable)
141
+ @configurable = configurable
142
+ end
143
+
144
+ def method_missing(name, *args, &block)
145
+ super unless block.nil? and args.empty?
146
+ super unless @configurable.respond_to?(name)
147
+ return ProxyValue.new(@configurable, @configurable.class.field_metadata(name))
148
+ end
149
+ end
150
+
151
+ class FieldProcessor
152
+ def initialize(source)
153
+ @source = source
154
+ @field_names = filter(source.class.field_names)
155
+ end
156
+ attr_accessor :field_names
157
+ attr_reader :source
158
+
159
+ def filter_attribute
160
+ raise NotImplementedError
161
+ end
162
+
163
+ def filter(field_names)
164
+ field_names.find_all do |name|
165
+ source.class.field_metadata(name).is?(filter_attribute)
166
+ end
167
+ end
168
+
169
+ def value(field)
170
+ source.__send__(field.reader_method)
171
+ end
172
+
173
+ def to(target)
174
+ field_names.each do |name|
175
+ field = source.class.field_metadata(name)
176
+ next unless target.respond_to?(field.writer_method)
177
+ target.__send__(field.writer_method, value(field))
178
+ end
179
+ end
180
+ end
181
+
182
+ class SettingsCopier < FieldProcessor
183
+ def filter_attribute
184
+ :copiable
185
+ end
186
+
187
+ def value(field)
188
+ field.immediate_value_on(source)
189
+ end
190
+ end
191
+
192
+ class SettingsProxier < FieldProcessor
193
+ def filter_attribute
194
+ :proxiable
195
+ end
196
+
197
+ def value(field)
198
+ ProxyValue.new(source, field)
21
199
  end
22
200
  end
23
- RequiredField.freeze
24
201
 
25
202
  #Describes class level DSL & machinery for working with configuration
26
203
  #managment.
@@ -46,76 +223,38 @@ module Mattock
46
223
  # ce.check_required #=> raises error because :must and :foo aren't set
47
224
  module ClassMethods
48
225
  def default_values
49
- @default_values ||= {}
50
- end
51
-
52
- def set_defaults_on(instance)
53
- if Configurable > superclass
54
- superclass.set_defaults_on(instance)
55
- end
56
- default_values.each_pair do |name,value|
57
- instance.__send__("#{name}=", value)
58
- if Configurable === value
59
- value.class.set_defaults_on(value)
60
- end
61
- end
226
+ @default_values ||= []
62
227
  end
63
228
 
64
- def missing_required_fields_on(instance)
65
- missing = []
229
+ def field_names
230
+ names = default_values.map{|field| field.name}
66
231
  if Configurable > superclass
67
- missing = superclass.missing_required_fields_on(instance)
232
+ names | superclass.field_names
233
+ else
234
+ names
68
235
  end
69
- default_values.each_pair do |name,value|
70
- set_value = instance.__send__(name)
71
- if value == RequiredField and set_value == RequiredField
72
- missing << name
73
- next
74
- end
75
- if Configurable === set_value
76
- missing += set_value.class.missing_required_fields_on(set_value).map do |field|
77
- [name, field].join(".")
78
- end
79
- end
80
- end
81
- return missing
82
236
  end
83
237
 
84
- def copy_settings(from, to)
85
- if Configurable > superclass
86
- superclass.copy_settings(from, to)
238
+ def field_metadata(name)
239
+ field = default_values.find{|field| field.name == name}
240
+ if field.nil? and Configurable > superclass
241
+ superclass.field_metadata(name)
242
+ else
243
+ field
87
244
  end
88
- default_values.keys.each do |field|
89
- begin
90
- to.__send__("#{field}=", from.__send__(field))
91
- rescue NoMethodError
92
- #shrug it off
93
- end
94
- end
95
- end
96
-
97
- def to_hash(obj)
98
- hash = if Configurable > superclass
99
- superclass.to_hash(obj)
100
- else
101
- {}
102
- end
103
- hash.merge( Hash[default_values.keys.zip(default_values.keys.map{|key|
104
- begin
105
- obj.__send__(key)
106
- rescue NoMethodError
107
- end
108
- }).to_a])
109
245
  end
110
246
 
111
247
  #Creates an anonymous Configurable - useful in complex setups for nested
112
248
  #settings
113
249
  #@example SSH options
114
250
  # setting :ssh => nested(:username => "me", :password => nil)
115
- def nested(hash=nil)
116
- obj = Class.new(Struct).new
117
- obj.settings(hash || {})
118
- return obj
251
+ def nested(hash=nil, &block)
252
+ nested = Class.new(Struct)
253
+ nested.settings(hash || {})
254
+ if block_given?
255
+ nested.instance_eval(&block)
256
+ end
257
+ return nested
119
258
  end
120
259
 
121
260
  #Quick list of setting fields with a default value of nil. Useful
@@ -124,6 +263,7 @@ module Mattock
124
263
  names.each do |name|
125
264
  setting(name, nil)
126
265
  end
266
+ self
127
267
  end
128
268
  alias nil_field nil_fields
129
269
 
@@ -133,18 +273,47 @@ module Mattock
133
273
  names.each do |name|
134
274
  setting(name)
135
275
  end
276
+ self
136
277
  end
137
278
  alias required_field required_fields
138
279
 
280
+ RequiredField = Object.new.freeze
281
+
139
282
  #Defines a setting on this class - much like a attr_accessible call, but
140
283
  #allows for defaults and required settings
141
284
  def setting(name, default_value = RequiredField)
142
285
  name = name.to_sym
143
- attr_accessor(name)
144
- if default_values.has_key?(name) and default_values[name] != default_value
145
- warn "Changing default value of #{self.name}##{name} from #{default_values[name].inspect} to #{default_value.inspect}"
286
+ metadata =
287
+ if default_value == RequiredField
288
+ FieldMetadata.new(name, nil).is(:required).isnt(:defaulting)
289
+ else
290
+ FieldMetadata.new(name, default_value)
291
+ end
292
+
293
+ attr_writer(name)
294
+ define_method(metadata.reader_method) do
295
+ value = metadata.value_on(self)
296
+ end
297
+
298
+ if existing = default_values.find{|field| field.name == name} and existing.default_value != default_value
299
+ source_line = caller.drop_while{|line| /#{__FILE__}/ =~ line}.first
300
+ warn "Changing default value of #{self.name}##{name} from #{existing.default_value.inspect} to #{default_value.inspect}"
301
+ " (at: #{source_line})"
146
302
  end
147
- default_values[name] = default_value
303
+ default_values << metadata
304
+ metadata
305
+ end
306
+
307
+ def runtime_required_fields(*names)
308
+ names.each do |name|
309
+ runtime_setting(name)
310
+ end
311
+ self
312
+ end
313
+ alias runtime_required_field runtime_required_fields
314
+
315
+ def runtime_setting(name, default_value = RequiredField)
316
+ setting(name, default_value).is(:runtime)
148
317
  end
149
318
 
150
319
  #@param [Hash] hash Pairs of name/value to be converted into
@@ -155,6 +324,87 @@ module Mattock
155
324
  end
156
325
  return self
157
326
  end
327
+ alias runtime_settings settings
328
+
329
+ def set_defaults_on(instance)
330
+ if Configurable > superclass
331
+ superclass.set_defaults_on(instance)
332
+ end
333
+ default_values.each do |field|
334
+ next unless field.is? :defaulting
335
+ value = field.default_value
336
+ if Module === value and Configurable > value
337
+ value = value.new
338
+ value.class.set_defaults_on(value)
339
+ end
340
+ instance.__send__(field.writer_method, value)
341
+ end
342
+ end
343
+
344
+ def missing_required_fields_on(instance)
345
+ missing = []
346
+ if Configurable > superclass
347
+ missing = superclass.missing_required_fields_on(instance)
348
+ end
349
+ default_values.each do |field|
350
+ if field.missing_on?(instance)
351
+ missing << field.name
352
+ else
353
+ set_value = instance.__send__(field.reader_method)
354
+ if Configurable === set_value
355
+ missing += set_value.class.missing_required_fields_on(set_value).map do |field|
356
+ [name, field].join(".")
357
+ end
358
+ end
359
+ end
360
+ end
361
+ return missing
362
+ end
363
+
364
+ def copy_settings(from, to, &block)
365
+ if Configurable > superclass
366
+ superclass.copy_settings(from, to, &block)
367
+ end
368
+ default_values.each do |field|
369
+ begin
370
+ value =
371
+ if block_given?
372
+ yield(from, field)
373
+ else
374
+ from.__send__(field.reader_method)
375
+ end
376
+ if Configurable === value
377
+ value = value.clone
378
+ end
379
+ to.__send__(field.writer_method, value)
380
+ rescue NoMethodError
381
+ #shrug it off
382
+ end
383
+ end
384
+ end
385
+
386
+ def to_hash(obj)
387
+ hash = if Configurable > superclass
388
+ superclass.to_hash(obj)
389
+ else
390
+ {}
391
+ end
392
+ hash.merge( Hash[default_values.map{|field|
393
+ begin
394
+ value = obj.__send__(field.reader_method)
395
+ value =
396
+ case value
397
+ when Configurable
398
+ value.to_hash
399
+ else
400
+ value
401
+ end
402
+ [field.name, value]
403
+ rescue NoMethodError
404
+ end
405
+ }])
406
+ end
407
+
158
408
 
159
409
  def included(mod)
160
410
  mod.extend ClassMethods
@@ -163,18 +413,37 @@ module Mattock
163
413
 
164
414
  extend ClassMethods
165
415
 
416
+ def copy_settings
417
+ SettingsCopier.new(self)
418
+ end
419
+
166
420
  def copy_settings_to(other)
167
- self.class.copy_settings(self, other)
421
+ copy_settings.to(other)
168
422
  self
169
423
  end
170
424
 
425
+ def proxy_settings
426
+ SettingsProxier.new(self)
427
+ end
428
+
429
+ def proxy_settings_to(other)
430
+ proxy_settings.to(other)
431
+ end
432
+
171
433
  def to_hash
172
434
  self.class.to_hash(self)
173
435
  end
174
436
 
437
+ def unset_defaults_guard
438
+ raise "Tried to check required settings before running setup_defaults"
439
+ end
440
+
175
441
  #Call during initialize to set default values on settings - if you're using
176
442
  #Configurable outside of Mattock, be sure this gets called.
177
443
  def setup_defaults
444
+ def self.unset_defaults_guard
445
+ end
446
+
178
447
  self.class.set_defaults_on(self)
179
448
  self
180
449
  end
@@ -182,6 +451,7 @@ module Mattock
182
451
  #Checks that all required fields have be set, otherwise raises an error
183
452
  #@raise RuntimeError if any required fields are unset
184
453
  def check_required
454
+ unset_defaults_guard
185
455
  missing = self.class.missing_required_fields_on(self)
186
456
  unless missing.empty?
187
457
  raise "Required field#{missing.length > 1 ? "s" : ""} #{missing.map{|field| field.to_s.inspect}.join(", ")} unset on #{self.inspect}"
@@ -189,33 +459,25 @@ module Mattock
189
459
  self
190
460
  end
191
461
 
192
- def unset?(value)
193
- value == RequiredField
194
- end
195
-
196
- def setting(name, default_value = nil)
197
- self.class.setting(name, default_value)
198
- instance_variable_set("@#{name}", default_value)
462
+ def proxy_value
463
+ ProxyDecorator.new(self)
199
464
  end
200
465
 
201
- def settings(hash)
202
- hash.each_pair do |name, value|
203
- setting(name, value)
204
- end
205
- return self
466
+ #XXX deprecate
467
+ def unset?(value)
468
+ value.nil?
206
469
  end
207
470
 
208
- def required_fields(*names)
209
- self.class.required_fields(*names)
210
- self
471
+ def field_unset?(name)
472
+ self.class.field_metadata(name).unset_on?(self)
211
473
  end
212
- alias required_field required_fields
213
474
 
214
- def nil_fields(*names)
215
- self.class.nil_fields(*names)
216
- self
475
+ def fail_unless_set(name)
476
+ if self.class.field_metadata(name).unset_on?(self)
477
+ raise "Assertion failed: Field #{name} unset"
478
+ end
479
+ true
217
480
  end
218
- alias nil_field nil_fields
219
481
 
220
482
  class Struct
221
483
  include Configurable
@@ -50,12 +50,18 @@ module Mattock
50
50
  def initialize(app_name, library_default_dir = nil)
51
51
  @app_name = app_name
52
52
  @valise = Valise::Set.define do
53
- rw "~/.#{@app_name}"
53
+ rw "~/.#{app_name}"
54
+ rw "~/.mattock/#{app_name}"
54
55
  rw "~/.mattock"
55
- rw "/usr/share/#{@app_name}"
56
+
57
+ rw "/usr/share/#{app_name}"
58
+ rw "/usr/share/mattock/#{app_name}"
56
59
  rw "/usr/share/mattock"
57
- rw "/etc/#{@app_name}"
60
+
61
+ rw "/etc/#{app_name}"
62
+ rw "/etc/mattock/#{app_name}"
58
63
  rw "/etc/mattock"
64
+
59
65
  ro library_default_dir unless library_default_dir.nil?
60
66
  ro from_here("default_configuration")
61
67
 
@@ -67,6 +73,12 @@ module Mattock
67
73
 
68
74
  attr_reader :loaded, :valise
69
75
 
76
+ #Add special file handling for a particular file
77
+ def register_file(name, type, merge)
78
+ @valise.add_handler(name, type, merge)
79
+ end
80
+
81
+ #Add a search path to look for configuration files
70
82
  def register_search_path(from_file)
71
83
  directory = File::expand_path("../.#{@app_name}", from_file)
72
84
  @valise.prepend_search_root(Valise::SearchRoot.new(directory))
@@ -1,12 +1,21 @@
1
1
  require 'mattock/command-task'
2
2
  module Mattock
3
3
  class RemoteCommandTask < CommandTask
4
- setting(:remote_server, nested(
5
- :address => "localhost",
4
+ runtime_setting(:remote_server, nested(
5
+ :address => nil,
6
6
  :user => nil
7
7
  ))
8
8
  setting(:ssh_options, [])
9
9
  nil_fields(:id_file, :free_arguments)
10
+ runtime_setting(:remote_target)
11
+
12
+ def resolve_runtime_configuration
13
+ self.remote_target ||= [remote_server.user, remote_server.address].compact.join('@') unless remote_server.address.nil?
14
+ end
15
+
16
+ def ssh_option(name, value)
17
+ ssh_options << "\"#{name}=#{value}\""
18
+ end
10
19
 
11
20
  def decorated(command_on_remote)
12
21
  fail "Need remote server for #{self.class.name}" unless remote_server.address
@@ -14,14 +23,13 @@ module Mattock
14
23
  raise "Empty remote command" if command_on_remote.nil?
15
24
  Mattock::WrappingChain.new do |cmd|
16
25
  cmd.add Mattock::CommandLine.new("ssh") do |cmd|
17
- cmd.options << "-u #{remote_server.user}" if remote_server.user
18
26
  cmd.options << "-i #{id_file}" if id_file
19
27
  unless ssh_options.empty?
20
28
  ssh_options.each do |opt|
21
- cmd.options "-o #{opt}"
29
+ cmd.options << "-o #{opt}"
22
30
  end
23
31
  end
24
- cmd.options << remote_server.address
32
+ cmd.options << remote_target
25
33
  end
26
34
  cmd.add Mattock::ShellEscaped.new(command_on_remote)
27
35
  end
data/lib/mattock/task.rb CHANGED
@@ -9,6 +9,7 @@ module Mattock
9
9
 
10
10
  module TaskMixin
11
11
  include CascadingDefinition
12
+ include DeferredDefinition
12
13
 
13
14
  setting :task_name
14
15
  setting :task_args
@@ -20,8 +21,11 @@ module Mattock
20
21
  end
21
22
 
22
23
  def self.included(mod)
23
- mod.class_eval{ extend ClassMethods }
24
24
  super
25
+ mod.class_eval do
26
+ extend ClassMethods
27
+ DeferredDefinition.add_settings(self)
28
+ end
25
29
  end
26
30
 
27
31
  def initialize(*args)
@@ -44,17 +48,34 @@ module Mattock
44
48
  def action
45
49
  end
46
50
 
51
+
52
+ module ChildTask
53
+ attr_accessor :source_task
54
+
55
+ def unset_defaults_guard
56
+ source_task.unset_defaults_guard
57
+ end
58
+
59
+ def inspect
60
+ "From: " + source_task.inspect
61
+ end
62
+ end
63
+
47
64
  =begin
48
65
  # I continue to look for an alternative here.
49
66
  # The trouble is that deep inside of define_task, Rake actually
50
67
  # instantiates the Task - so in wanting to be able to override members of
51
68
  # Task, it's hard to get the virtues of CascadingDefinition as well (maybe
52
69
  # the virtues could be had without the actual mixin?)
70
+ #
71
+ # So, what we're doing is to dynamically create a child class and then
72
+ # carry forward the Rake::Task#initialize
53
73
  =end
54
74
  def task_class
55
75
  return @task_class if @task_class
56
76
  @task_class = Class.new(self.class) do
57
77
  define_method :initialize, Rake::Task.instance_method(:initialize)
78
+ include ChildTask
58
79
  end
59
80
  end
60
81
 
@@ -64,8 +85,10 @@ module Mattock
64
85
 
65
86
  def define
66
87
  task = task_class.define_task(*task_args) do
88
+ finalize_configuration
67
89
  task.action
68
90
  end
91
+ task.source_task = self
69
92
  copy_settings_to(task)
70
93
  end
71
94
  end
@@ -43,9 +43,9 @@ module Mattock
43
43
  attr_writer :namespace_name
44
44
 
45
45
  #The namespace this lib's tasks will created within. Changeable at
46
- #instatiation
46
+ #instantiation
47
47
  def self.default_namespace(name)
48
- setting(:namespace_name, name)
48
+ setting(:namespace_name, name).isnt(:copiable)
49
49
  end
50
50
 
51
51
  attr_reader :tasks
@@ -62,6 +62,18 @@ module Mattock
62
62
  return a_task
63
63
  end
64
64
 
65
+ def bracket_task(before, name, after)
66
+ task self[name] => before
67
+ task after => self[name]
68
+ end
69
+
70
+ def task_spine(*list)
71
+ task list.first
72
+ list.each_cons(2) do |first, second|
73
+ task second => first
74
+ end
75
+ end
76
+
65
77
  #@overload in_namespace(args)
66
78
  # maps the arguments to namespace-prefixed names, for use in Rake
67
79
  # dependency declaration
@@ -26,12 +26,13 @@ module Mattock
26
26
  end
27
27
 
28
28
  if base_path.empty?
29
- raise "Relative root #{up_to} not found in #{abs_path}"
29
+ raise "Relative root #{up_to.inspect} not found in #{abs_path.inspect}"
30
30
  end
31
31
 
32
32
  return base_path
33
33
  end
34
34
  module_function :rel_dir, :default_valise
35
+ public :rel_dir, :default_valise
35
36
  end
36
37
 
37
38
  module TemplateHost
@@ -60,7 +61,12 @@ module Mattock
60
61
  end
61
62
  end
62
63
 
63
- template.render(self)
64
+ locals = {}
65
+ if block_given?
66
+ yield locals
67
+ end
68
+
69
+ template.render(self, locals)
64
70
  end
65
71
  end
66
72
 
@@ -68,18 +68,21 @@ module Mattock
68
68
  "#{prefix[0]}.#{name}"
69
69
  end
70
70
 
71
+ def setting_method_name
72
+ "setting"
73
+ end
74
+
71
75
  def synthetic_setting(name, value=nil)
72
76
  args = s( s(:string_literal, s(:string_content, s(:tstring_content, name))))
73
77
  args << value unless value.nil?
74
78
  args << false
75
- new_call = s(:fcall, s(:ident, "setting"), s(:arg_paren, args))
79
+ new_call = s(:fcall, s(:ident, setting_method_name), s(:arg_paren, args))
76
80
  new_call.line_range = (1..1)
77
81
  new_call.traverse do |node|
78
82
  node.full_source ||= ""
79
83
  end
80
- new_call.full_source = "setting('#{name}'#{value.nil? ? "" : ", #{value.source}"})"
84
+ new_call.full_source = "#{setting_method_name}('#{name}'#{value.nil? ? "" : ", #{value.source}"})"
81
85
  new_call
82
-
83
86
  end
84
87
 
85
88
  def process
@@ -152,6 +155,24 @@ module Mattock
152
155
  end
153
156
  end
154
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
+
155
176
  class RequiredFieldsHandler < SettingHandler
156
177
  handles method_call(:required_field)
157
178
  handles method_call(:required_fields)
data/spec/command-line.rb CHANGED
@@ -46,6 +46,52 @@ describe Mattock::CommandLine do
46
46
  end
47
47
  end
48
48
 
49
+ describe Mattock::CommandLineDSL do
50
+ include described_class
51
+
52
+ describe "using the - operator" do
53
+ let :command do
54
+ cmd("sudo") - ["gem", "install", "bundler"]
55
+ end
56
+
57
+ it "should define commands" do
58
+ command.should be_an_instance_of(Mattock::WrappingChain)
59
+ command.should have(2).commands
60
+ command.commands[0].should be_an_instance_of(Mattock::CommandLine)
61
+ command.commands[1].should be_an_instance_of(Mattock::CommandLine)
62
+ command.command.should == "sudo -- gem install bundler"
63
+ end
64
+ end
65
+
66
+ describe "using the | operator" do
67
+ let :command do
68
+ cmd("cat", "/etc/passwd") | ["grep", "root"]
69
+ end
70
+
71
+ it "should define commands" do
72
+ command.should be_an_instance_of(Mattock::PipelineChain)
73
+ command.should have(2).commands
74
+ command.commands[0].should be_an_instance_of(Mattock::CommandLine)
75
+ command.commands[1].should be_an_instance_of(Mattock::CommandLine)
76
+ command.command.should == "cat /etc/passwd | grep root"
77
+ end
78
+ end
79
+
80
+ describe "using the && operator" do
81
+ let :command do
82
+ cmd("cd", "/tmp/trash") & %w{rm -rf *}
83
+ end
84
+
85
+ it "should define commands" do
86
+ command.should be_an_instance_of(Mattock::PrereqChain)
87
+ command.should have(2).commands
88
+ command.commands[0].should be_an_instance_of(Mattock::CommandLine)
89
+ command.commands[1].should be_an_instance_of(Mattock::CommandLine)
90
+ command.command.should == "cd /tmp/trash && rm -rf *"
91
+ end
92
+ end
93
+ end
94
+
49
95
  describe Mattock::CommandLine, "that fails" do
50
96
  let :commandline do
51
97
  Mattock::CommandLine.new("false")
data/spec/configurable.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'mattock'
2
+
1
3
  describe Mattock::Configurable do
2
4
  class TestSuperStruct
3
5
  include Mattock::Configurable
@@ -7,7 +9,7 @@ describe Mattock::Configurable do
7
9
  end
8
10
 
9
11
  class TestStruct < TestSuperStruct
10
- settings(:one => 1, :two => nested(:a => "a").required_field(:b))
12
+ settings(:one => 1, :two => nested(:a => "a"){ required_field(:b)} )
11
13
  nil_field(:five)
12
14
  end
13
15
 
@@ -22,8 +24,15 @@ describe Mattock::Configurable do
22
24
  subject.five.should be_nil
23
25
  end
24
26
 
27
+ it "#to_hash" do
28
+ hash = subject.to_hash
29
+ hash[:one].should == 1
30
+ hash[:two][:a].should == "a"
31
+ end
32
+
25
33
  it "should complain about unset required fields" do
26
34
  expect do
35
+ p subject
27
36
  subject.check_required
28
37
  end.to raise_error
29
38
  end
@@ -42,4 +51,50 @@ describe Mattock::Configurable do
42
51
  subject.check_required
43
52
  end.to_not raise_error
44
53
  end
54
+
55
+ describe "copying settings" do
56
+ class LeftStruct
57
+ include Mattock::Configurable
58
+
59
+ setting(:normal, 1)
60
+ setting(:no_copy, 2).isnt(:copiable)
61
+ setting(:no_proxy, 3).isnt(:proxiable)
62
+ setting(:no_nothing, 4).isnt(:copiable).isnt(:proxiable)
63
+ setting(:not_on_target, 5)
64
+ end
65
+
66
+ class RightStruct
67
+ include Mattock::Configurable
68
+
69
+ required_fields(:normal, :no_copy, :no_proxy, :no_nothing)
70
+ end
71
+
72
+ let :left do
73
+ LeftStruct.new.setup_defaults
74
+ end
75
+
76
+ let :right do
77
+ RightStruct.new.setup_defaults
78
+ end
79
+
80
+ it "should not copy no_copy" do
81
+ left.copy_settings.to(right)
82
+ right.unset?(right.normal).should be_false
83
+ right.normal.should == 1
84
+ right.unset?(right.no_copy).should be_true
85
+ right.unset?(right.no_proxy).should be_false
86
+ right.no_proxy.should == 3
87
+ right.unset?(right.no_nothing).should be_true
88
+ end
89
+
90
+ it "should not proxy no_proxy" do
91
+ left.proxy_settings.to(right)
92
+ right.unset?(right.normal).should be_false
93
+ right.normal.should == 1
94
+ right.unset?(right.no_copy).should be_false
95
+ right.no_copy.should == 2
96
+ right.unset?(right.no_proxy).should be_true
97
+ right.unset?(right.no_nothing).should be_true
98
+ end
99
+ end
45
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mattock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-12 00:00:00.000000000 Z
12
+ date: 2012-10-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: corundum
16
- requirement: &76587950 !ruby/object:Gem::Requirement
16
+ requirement: &82047460 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,21 +21,24 @@ dependencies:
21
21
  version: 0.0.1
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *76587950
24
+ version_requirements: *82047460
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: valise
27
- requirement: &76587340 !ruby/object:Gem::Requirement
27
+ requirement: &82046930 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
32
  version: '0.6'
33
+ segments:
34
+ - 0
35
+ - 6
33
36
  type: :runtime
34
37
  prerelease: false
35
- version_requirements: *76587340
38
+ version_requirements: *82046930
36
39
  - !ruby/object:Gem::Dependency
37
40
  name: tilt
38
- requirement: &76587100 !ruby/object:Gem::Requirement
41
+ requirement: &82046620 !ruby/object:Gem::Requirement
39
42
  none: false
40
43
  requirements:
41
44
  - - ! '>'
@@ -45,7 +48,7 @@ dependencies:
45
48
  - 0
46
49
  type: :runtime
47
50
  prerelease: false
48
- version_requirements: *76587100
51
+ version_requirements: *82046620
49
52
  description: ! " If Rake won't do it by itself, you oughtta Mattock.\n\n If you
50
53
  survived the pun, you might enjoy this gem.\n\n Features:\n\n * Extensions to
51
54
  Tasklibs to support powerful deerpaths.\n * A commandline library that supports
@@ -98,7 +101,7 @@ rdoc_options:
98
101
  - --main
99
102
  - doc/README
100
103
  - --title
101
- - mattock-0.2.13 RDoc
104
+ - mattock-0.3.0 RDoc
102
105
  require_paths:
103
106
  - lib/
104
107
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -109,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
112
  version: '0'
110
113
  segments:
111
114
  - 0
112
- hash: 332286803
115
+ hash: 900034003
113
116
  required_rubygems_version: !ruby/object:Gem::Requirement
114
117
  none: false
115
118
  requirements: