mattock 0.4.1 → 0.5.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/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
|