mattock 0.2.13 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mattock/cascading-definition.rb +35 -0
- data/lib/mattock/command-line.rb +39 -2
- data/lib/mattock/command-task.rb +8 -4
- data/lib/mattock/configurable.rb +350 -88
- data/lib/mattock/configuration-store.rb +15 -3
- data/lib/mattock/remote-command-task.rb +13 -5
- data/lib/mattock/task.rb +24 -1
- data/lib/mattock/tasklib.rb +14 -2
- data/lib/mattock/template-host.rb +8 -2
- data/lib/mattock/yard_extensions.rb +24 -3
- data/spec/command-line.rb +46 -0
- data/spec/configurable.rb +56 -1
- metadata +13 -10
@@ -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
|
data/lib/mattock/command-line.rb
CHANGED
@@ -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 #{
|
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] +
|
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
|
data/lib/mattock/command-task.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
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(
|
18
|
-
|
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
|
data/lib/mattock/configurable.rb
CHANGED
@@ -10,17 +10,194 @@ module Mattock
|
|
10
10
|
#
|
11
11
|
#@example (see ClassMethods)
|
12
12
|
module Configurable
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
65
|
-
|
229
|
+
def field_names
|
230
|
+
names = default_values.map{|field| field.name}
|
66
231
|
if Configurable > superclass
|
67
|
-
|
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
|
85
|
-
|
86
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
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
|
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
|
-
|
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
|
193
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
204
|
-
end
|
205
|
-
return self
|
466
|
+
#XXX deprecate
|
467
|
+
def unset?(value)
|
468
|
+
value.nil?
|
206
469
|
end
|
207
470
|
|
208
|
-
def
|
209
|
-
self.class.
|
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
|
215
|
-
self.class.
|
216
|
-
|
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 "~/.#{
|
53
|
+
rw "~/.#{app_name}"
|
54
|
+
rw "~/.mattock/#{app_name}"
|
54
55
|
rw "~/.mattock"
|
55
|
-
|
56
|
+
|
57
|
+
rw "/usr/share/#{app_name}"
|
58
|
+
rw "/usr/share/mattock/#{app_name}"
|
56
59
|
rw "/usr/share/mattock"
|
57
|
-
|
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
|
-
|
5
|
-
:address =>
|
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 <<
|
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
|
data/lib/mattock/tasklib.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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,
|
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 = "
|
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")
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *82047460
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: valise
|
27
|
-
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: *
|
38
|
+
version_requirements: *82046930
|
36
39
|
- !ruby/object:Gem::Dependency
|
37
40
|
name: tilt
|
38
|
-
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: *
|
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.
|
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:
|
115
|
+
hash: 900034003
|
113
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
117
|
none: false
|
115
118
|
requirements:
|