mattock 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mattock/command-line.rb +16 -69
- data/lib/mattock/command-line/command-run-result.rb +131 -0
- data/lib/mattock/command-task.rb +5 -1
- data/lib/mattock/configurable.rb +8 -546
- data/lib/mattock/configurable/class-methods.rb +208 -0
- data/lib/mattock/configurable/directory-structure.rb +147 -0
- data/lib/mattock/configurable/field-metadata.rb +125 -0
- data/lib/mattock/configurable/field-processor.rb +54 -0
- data/lib/mattock/configurable/instance-methods.rb +75 -0
- data/lib/mattock/configurable/proxy-value.rb +30 -0
- data/lib/mattock/remote-command-task.rb +12 -11
- data/lib/mattock/task.rb +5 -0
- data/lib/mattock/tasklib.rb +22 -2
- data/lib/mattock/template-host.rb +3 -0
- data/lib/mattock/testing/mock-command-line.rb +1 -1
- data/spec/command-line.rb +1 -1
- data/spec/command-task.rb +4 -0
- data/spec/configurable.rb +22 -1
- data/spec/template-host.rb +1 -1
- data/spec_help/spec_helper.rb +7 -0
- metadata +29 -26
@@ -0,0 +1,208 @@
|
|
1
|
+
module Mattock
|
2
|
+
module Configurable
|
3
|
+
RequiredField = Object.new.freeze
|
4
|
+
|
5
|
+
#Describes class level DSL & machinery for working with configuration
|
6
|
+
#managment.
|
7
|
+
#
|
8
|
+
#@example
|
9
|
+
# class ConfExample
|
10
|
+
# include Configurable
|
11
|
+
#
|
12
|
+
# setting :foo
|
13
|
+
# settings :bar => 1, :baz => 3
|
14
|
+
# nil_fields :hoo, :ha, :harum
|
15
|
+
# required_fields :must
|
16
|
+
#
|
17
|
+
# def initialize
|
18
|
+
# setup_defaults
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# ce = ConfExample.new
|
23
|
+
# ce.bar #=> 1
|
24
|
+
# ce.hoo #=> nil
|
25
|
+
# ce.hoo = "hallo"
|
26
|
+
# ce.check_required #=> raises error because :must and :foo aren't set
|
27
|
+
module ClassMethods
|
28
|
+
def inspect_instance(instance, indent="")
|
29
|
+
field_names.map do |name|
|
30
|
+
meta = field_metadata(name)
|
31
|
+
"#{indent}#{meta.inspect_on(instance, indent * 2)}"
|
32
|
+
end.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_values
|
36
|
+
@default_values ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def field_names
|
40
|
+
names = default_values.map{|field| field.name}
|
41
|
+
if Configurable > superclass
|
42
|
+
names | superclass.field_names
|
43
|
+
else
|
44
|
+
names
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def field_metadata(name)
|
49
|
+
field = default_values.find{|field| field.name == name}
|
50
|
+
if field.nil? and Configurable > superclass
|
51
|
+
superclass.field_metadata(name)
|
52
|
+
else
|
53
|
+
field
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#@raises NoDefaultValue
|
58
|
+
def default_value_for(name)
|
59
|
+
field = field_metadata(name)
|
60
|
+
raise NoDefaultValue.new(name,self) unless field.is?(:defaulting)
|
61
|
+
return field.default_value
|
62
|
+
end
|
63
|
+
|
64
|
+
#Creates an anonymous Configurable - useful in complex setups for nested
|
65
|
+
#settings
|
66
|
+
#@example SSH options
|
67
|
+
# setting :ssh => nested(:username => "me", :password => nil)
|
68
|
+
def nested(hash=nil, &block)
|
69
|
+
nested = Class.new(Struct)
|
70
|
+
nested.settings(hash || {})
|
71
|
+
if block_given?
|
72
|
+
nested.instance_eval(&block)
|
73
|
+
end
|
74
|
+
return nested
|
75
|
+
end
|
76
|
+
|
77
|
+
#Quick list of setting fields with a default value of nil. Useful
|
78
|
+
#especially with {CascadingDefinition#resolve_configuration}
|
79
|
+
def nil_fields(*names)
|
80
|
+
names.each do |name|
|
81
|
+
setting(name, nil)
|
82
|
+
end
|
83
|
+
self
|
84
|
+
end
|
85
|
+
alias nil_field nil_fields
|
86
|
+
|
87
|
+
#List fields with no default for with a value must be set before
|
88
|
+
#definition.
|
89
|
+
def required_fields(*names)
|
90
|
+
names.each do |name|
|
91
|
+
setting(name)
|
92
|
+
end
|
93
|
+
self
|
94
|
+
end
|
95
|
+
alias required_field required_fields
|
96
|
+
|
97
|
+
#Defines a setting on this class - much like a attr_accessible call, but
|
98
|
+
#allows for defaults and required settings
|
99
|
+
def setting(name, default_value = RequiredField)
|
100
|
+
name = name.to_sym
|
101
|
+
metadata =
|
102
|
+
if default_value == RequiredField
|
103
|
+
FieldMetadata.new(name, nil).is(:required).isnt(:defaulting)
|
104
|
+
else
|
105
|
+
FieldMetadata.new(name, default_value)
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_writer(name)
|
109
|
+
define_method(metadata.reader_method) do
|
110
|
+
value = metadata.value_on(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
if existing = default_values.find{|field| field.name == name} and existing.default_value != default_value
|
114
|
+
source_line = caller.drop_while{|line| /#{__FILE__}/ =~ line}.first
|
115
|
+
warn "Changing default value of #{self.name}##{name} from #{existing.default_value.inspect} to #{default_value.inspect} (at: #{source_line})"
|
116
|
+
end
|
117
|
+
default_values << metadata
|
118
|
+
metadata
|
119
|
+
end
|
120
|
+
|
121
|
+
def runtime_required_fields(*names)
|
122
|
+
names.each do |name|
|
123
|
+
runtime_setting(name)
|
124
|
+
end
|
125
|
+
self
|
126
|
+
end
|
127
|
+
alias runtime_required_field runtime_required_fields
|
128
|
+
|
129
|
+
def runtime_setting(name, default_value = RequiredField)
|
130
|
+
setting(name, default_value).is(:runtime)
|
131
|
+
end
|
132
|
+
|
133
|
+
#@param [Hash] hash Pairs of name/value to be converted into
|
134
|
+
# setting/default
|
135
|
+
def settings(hash)
|
136
|
+
hash.each_pair do |name, value|
|
137
|
+
setting(name, value)
|
138
|
+
end
|
139
|
+
return self
|
140
|
+
end
|
141
|
+
alias runtime_settings settings
|
142
|
+
|
143
|
+
def set_defaults_on(instance)
|
144
|
+
if Configurable > superclass
|
145
|
+
superclass.set_defaults_on(instance)
|
146
|
+
end
|
147
|
+
default_values.each do |field|
|
148
|
+
next unless field.is? :defaulting
|
149
|
+
value = field.default_value
|
150
|
+
if Module === value and Configurable > value
|
151
|
+
value = value.new
|
152
|
+
value.class.set_defaults_on(value)
|
153
|
+
end
|
154
|
+
instance.__send__(field.writer_method, value)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def missing_required_fields_on(instance)
|
159
|
+
missing = []
|
160
|
+
if Configurable > superclass
|
161
|
+
missing = superclass.missing_required_fields_on(instance)
|
162
|
+
end
|
163
|
+
default_values.each do |field|
|
164
|
+
if field.missing_on?(instance)
|
165
|
+
missing << field.name
|
166
|
+
else
|
167
|
+
set_value = instance.__send__(field.reader_method)
|
168
|
+
if Configurable === set_value
|
169
|
+
missing += set_value.class.missing_required_fields_on(set_value).map do |sub_field|
|
170
|
+
[field.name, sub_field].join(".")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
return missing
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_hash(obj)
|
179
|
+
hash = if Configurable > superclass
|
180
|
+
superclass.to_hash(obj)
|
181
|
+
else
|
182
|
+
{}
|
183
|
+
end
|
184
|
+
hash.merge( Hash[default_values.map{|field|
|
185
|
+
begin
|
186
|
+
value = obj.__send__(field.reader_method)
|
187
|
+
value =
|
188
|
+
case value
|
189
|
+
when Configurable
|
190
|
+
value.to_hash
|
191
|
+
else
|
192
|
+
value
|
193
|
+
end
|
194
|
+
[field.name, value]
|
195
|
+
rescue NoMethodError
|
196
|
+
end
|
197
|
+
}])
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
def included(mod)
|
202
|
+
mod.extend ClassMethods
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
extend ClassMethods
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Mattock
|
2
|
+
module Configurable
|
3
|
+
class MissingRelativePaths < Exception; end
|
4
|
+
|
5
|
+
#XXX Consider making the actual dir/path settings r/o
|
6
|
+
#Very easy to say
|
7
|
+
# filename = "string"
|
8
|
+
#rather than
|
9
|
+
# filename.relative_path = "string"
|
10
|
+
#and it isn't clear which (abs/rel) you mean
|
11
|
+
#
|
12
|
+
module DirectoryStructure
|
13
|
+
class StructurePath
|
14
|
+
include Configurable
|
15
|
+
|
16
|
+
setting :absolute_path
|
17
|
+
setting :relative_path
|
18
|
+
|
19
|
+
def initialize(rel_path)
|
20
|
+
self.relative_path = rel_path unless rel_path == Configurable::RequiredField
|
21
|
+
end
|
22
|
+
|
23
|
+
def pathname
|
24
|
+
@pathname ||=
|
25
|
+
begin
|
26
|
+
fail_unless_set(:absolute_path)
|
27
|
+
require 'pathname'
|
28
|
+
Pathname.new(absolute_path)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias path_name pathname
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"<path: #{
|
35
|
+
if unset?(:absolute_path)
|
36
|
+
"<<?>>/#{relative_path.inspect}"
|
37
|
+
else
|
38
|
+
absolute_path.inspect
|
39
|
+
end
|
40
|
+
}>"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
RequiredField = Configurable::RequiredField
|
46
|
+
|
47
|
+
def root_paths
|
48
|
+
@root_paths ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
def path_heirarchy
|
52
|
+
@path_heirarchy ||= []
|
53
|
+
end
|
54
|
+
attr_writer :path_heirarchy
|
55
|
+
|
56
|
+
def path_fields
|
57
|
+
@path_fields ||= []
|
58
|
+
end
|
59
|
+
|
60
|
+
def dir(field_name, *args)
|
61
|
+
rel_path = RequiredField
|
62
|
+
if String === args.first
|
63
|
+
rel_path = args.shift
|
64
|
+
end
|
65
|
+
parent_field = path(field_name, rel_path)
|
66
|
+
|
67
|
+
root_paths << parent_field
|
68
|
+
self.path_heirarchy += args.map do |child_field|
|
69
|
+
[parent_field, child_field]
|
70
|
+
end
|
71
|
+
return parent_field
|
72
|
+
end
|
73
|
+
|
74
|
+
def path(field_name, rel_path=RequiredField)
|
75
|
+
field = setting(field_name, StructurePath.new(rel_path))
|
76
|
+
root_paths << field
|
77
|
+
path_fields << field
|
78
|
+
return field
|
79
|
+
end
|
80
|
+
|
81
|
+
def resolve_path_on(instance, parent, child_field, missing_relatives)
|
82
|
+
child = child_field.value_on(instance)
|
83
|
+
return unless child.field_unset?(:absolute_path)
|
84
|
+
if child.field_unset?(:relative_path)
|
85
|
+
missing_relatives << child_field
|
86
|
+
return
|
87
|
+
end
|
88
|
+
child.absolute_path = File::join(parent.absolute_path, child.relative_path)
|
89
|
+
end
|
90
|
+
|
91
|
+
def resolve_paths_on(instance)
|
92
|
+
superclass_exception = nil
|
93
|
+
if superclass < DirectoryStructure
|
94
|
+
begin
|
95
|
+
superclass.resolve_paths_on(instance)
|
96
|
+
rescue MissingRelativePaths => mrp
|
97
|
+
superclass_exception = mrp
|
98
|
+
end
|
99
|
+
end
|
100
|
+
missing_relatives = []
|
101
|
+
|
102
|
+
(root_paths - path_heirarchy.map{|_, child| child }).each do |field|
|
103
|
+
resolve_path_on(instance, instance, field, missing_relatives)
|
104
|
+
end
|
105
|
+
|
106
|
+
path_heirarchy.reverse.each do |parent_field, child_field|
|
107
|
+
parent = parent_field.value_on(instance)
|
108
|
+
resolve_path_on(instance, parent, child_field, missing_relatives)
|
109
|
+
end
|
110
|
+
|
111
|
+
case [missing_relatives.empty?, superclass_exception.nil?]
|
112
|
+
when [true, false]
|
113
|
+
raise superclass_exception
|
114
|
+
when [false, true]
|
115
|
+
raise MissingRelativePaths, "Required field#{missing_relatives.length == 1 ? "" : "s"} #{missing_relatives.map{|field| "#{field.name}.relative_path".inspect}.join(", ")} unset on #{self.inspect}"
|
116
|
+
when [false, false]
|
117
|
+
raise MissingRelativePaths, "Required field#{missing_relatives.length == 1 ? "" : "s"} #{missing_relatives.map{|field| "#{field.name}.relative_path".inspect}.join(", ")} unset on #{self.inspect}" + "\n" + superclass_exception.message
|
118
|
+
end
|
119
|
+
|
120
|
+
path_fields.each do |field|
|
121
|
+
value = field.value_on(instance)
|
122
|
+
next unless value.field_unset?(:relative_path)
|
123
|
+
value.relative_path = value.absolute_path
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.included(sub)
|
129
|
+
sub.extend ClassMethods
|
130
|
+
dir_path =
|
131
|
+
if not (file_path = ::Rake.application.rakefile).nil?
|
132
|
+
File::dirname(File::expand_path(file_path))
|
133
|
+
elsif not (dir_path = ::Rake.application.original_dir).nil?
|
134
|
+
dir_path
|
135
|
+
else
|
136
|
+
file_path = caller[0].split(':')[0]
|
137
|
+
File::dirname(File::expand_path(file_path))
|
138
|
+
end
|
139
|
+
sub.setting :absolute_path, dir_path
|
140
|
+
end
|
141
|
+
|
142
|
+
def resolve_paths
|
143
|
+
self.class.resolve_paths_on(self)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Mattock
|
2
|
+
module Configurable
|
3
|
+
class FieldMetadata
|
4
|
+
attr_accessor :name, :default_value
|
5
|
+
|
6
|
+
DEFAULT_PROPERTIES = {
|
7
|
+
:copiable => true,
|
8
|
+
:proxiable => true,
|
9
|
+
:required => false,
|
10
|
+
:runtime => false,
|
11
|
+
:defaulting => true,
|
12
|
+
}
|
13
|
+
def initialize(name, value)
|
14
|
+
@name = name
|
15
|
+
@default_value = value
|
16
|
+
@properties = DEFAULT_PROPERTIES.clone
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
set_props = DEFAULT_PROPERTIES.keys.find_all do |prop|
|
21
|
+
@properties[prop]
|
22
|
+
end
|
23
|
+
"Field: #{name}: #{default_value.inspect} #{set_props.inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect_on(instance, indent=nil)
|
27
|
+
set_props = DEFAULT_PROPERTIES.keys.find_all do |prop|
|
28
|
+
@properties[prop]
|
29
|
+
end
|
30
|
+
"Field: #{name}: #{value_on(instance).inspect} \n#{indent||""}(default: #{default_value.inspect} immediate: #{immediate_value_on(instance).inspect}) #{set_props.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_property_name(name)
|
34
|
+
unless DEFAULT_PROPERTIES.has_key?(name)
|
35
|
+
raise "Invalid field property #{name.inspect} - valid are: #{DEFAULT_PROPERTIES.keys.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def is?(property)
|
40
|
+
validate_property_name(property)
|
41
|
+
@properties[property]
|
42
|
+
end
|
43
|
+
|
44
|
+
def is_not?(property)
|
45
|
+
validate_property_name(property)
|
46
|
+
!@properties[property]
|
47
|
+
end
|
48
|
+
alias isnt? is_not?
|
49
|
+
|
50
|
+
def is(property)
|
51
|
+
validate_property_name(property)
|
52
|
+
@properties[property] = true
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_not(property)
|
57
|
+
validate_property_name(property)
|
58
|
+
@properties[property] = false
|
59
|
+
self
|
60
|
+
end
|
61
|
+
alias isnt is_not
|
62
|
+
|
63
|
+
def ivar_name
|
64
|
+
"@#{name}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def writer_method
|
68
|
+
"#{name}="
|
69
|
+
end
|
70
|
+
|
71
|
+
def reader_method
|
72
|
+
name
|
73
|
+
end
|
74
|
+
|
75
|
+
def immediate_value_on(instance)
|
76
|
+
instance.instance_variable_get(ivar_name)
|
77
|
+
#instance.__send__(reader_method)
|
78
|
+
end
|
79
|
+
|
80
|
+
def value_on(instance)
|
81
|
+
value = immediate_value_on(instance)
|
82
|
+
if ProxyValue === value
|
83
|
+
value.field.value_on(value.source)
|
84
|
+
else
|
85
|
+
value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_on?(instance)
|
90
|
+
return true unless instance.__send__(reader_method).nil?
|
91
|
+
return false unless instance.instance_variable_defined?(ivar_name)
|
92
|
+
value = immediate_value_on(instance)
|
93
|
+
if ProxyValue === value
|
94
|
+
value.field.set_on?(value.source)
|
95
|
+
else
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def unset_on?(instance)
|
101
|
+
!set_on?(instance)
|
102
|
+
end
|
103
|
+
|
104
|
+
def missing_on?(instance)
|
105
|
+
return false unless is?(:required)
|
106
|
+
if instance.respond_to?(:runtime?) and !instance.runtime?
|
107
|
+
return runtime_missing_on?(instance)
|
108
|
+
else
|
109
|
+
return !set_on?(instance)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def runtime_missing_on?(instance)
|
114
|
+
return false if is?(:runtime)
|
115
|
+
return true unless instance.instance_variable_defined?(ivar_name)
|
116
|
+
value = immediate_value_on(instance)
|
117
|
+
if ProxyValue === value
|
118
|
+
value.field.runtime_missing_on?(value.source)
|
119
|
+
else
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|