mattock 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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