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.
- 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:
|