mattock 0.2.13 → 0.3.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.
@@ -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: