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