cascading_classes 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,94 +1,147 @@
1
+
2
+ # todo: blocks in each property definition??
3
+
1
4
  module CascadingClasses
2
5
 
3
- # based off
4
- # http://blog.oleganza.com/post/115377756/correct-blankslate-in-ruby
5
6
  class BlankSlate
6
7
  class << self; alias __undef_method undef_method; end
7
8
  keep = [:instance_eval, :object_id]
8
- ancestors.inject([]){|res, a| res + (a.instance_methods - keep)}.uniq.
9
- each{|m| (__undef_method(m) rescue nil) unless m =~ /^__/ }
9
+ ancestors.inject([]){|res, a|
10
+ res + (a.instance_methods - keep)
11
+ }.uniq.each{|m|
12
+ (__undef_method(m) rescue nil) unless m =~ /^__/
13
+ }
10
14
  end
11
-
15
+
12
16
  class DSL < BlankSlate
13
17
 
18
+ ## def method_missing(meth, *args, &block)
14
19
  def method_missing(meth, *args, &block)
20
+ ## args.unshift(Hash.new) if block_given? # hackish: turns empty block into a hash
15
21
  case meth
16
22
  when /inspect/
17
23
  super
18
- when /set_property/
19
- __property__(args[0], *args[1..-1], &block)
20
24
  else
21
25
  __property__(meth, *args, &block)
22
26
  end
23
27
  end
24
28
 
25
29
  def __property__(name, args={}, &block)
30
+ name = name.to_sym
31
+
26
32
  @names << name unless @names.include? name
27
33
 
28
- @defaults[name] = if args.include? :default
29
- args[:default]
30
- elsif args.include? :initial
31
- args[:initial]
32
- elsif args.include? :start
33
- args[:start]
34
- elsif args.include? :begin
35
- args[:begin]
36
- else
37
- nil
34
+ args.keys.each{|k| args[k.to_sym] = args.delete(k) unless Symbol === k}
35
+
36
+ t = [:default, :start, :begin, :data, :template].detect{|v| args.include?(v)}
37
+ ## t = [:default].detect{|v| args.include?(v)}
38
+ if t
39
+ @default[name] = args[t]
40
+ ## (@options_given[name] ||= []) << :default
38
41
  end
39
42
 
40
- @instances_too[name] = if args.include? :classes_only
41
- !args[:classes_only]
42
- elsif args.include? :only_classes
43
- !args[:only_classes]
44
- elsif args.include? :classes_only
45
- !args[:classes_only]
46
- elsif args.include? :exclude_instances
47
- !args[:exclude_instances]
48
- elsif args.include? :skip_instances
49
- !args[:skip_instances]
50
- elsif args.include? :not_instances
51
- !args[:not_instances]
52
- elsif args.include? :apply_to_instances
53
- args[:apply_to_instances]
54
- elsif args.include? :both
55
- args[:both]
56
- elsif args.include? :instances_too
57
- args[:instances_too]
58
- elsif args.include? :instances
59
- args[:instances]
60
- elsif args.include? :instance
61
- args[:instance]
62
- else
63
- @apply_to_instances
43
+ t = [:classes_only, :only_classes, :exclude_instances, :no_instances,
44
+ :not_instances, :skip_instances].detect{|v| args.include?(v)}
45
+ s = [:instances, :apply_to_instances, :both, :classes_and_instances,
46
+ :instances_and_classes, :instances_too, :instance].detect{|v| args.include?(v)}
47
+ @instances_too[name] = nil
48
+ @instances_too[name] = !args[t] if t
49
+ @instances_too[name] = !!args[s] if s
50
+ @instances_too[name] = @apply_to_instances unless t or s
51
+
52
+ t = [:getter, :getters, :reader, :readers].detect{|v| args.include?(v)}
53
+ @getters[name] = t ? [*args[t]] : [name]
54
+
55
+ t = [:setter, :setters, :writer, :writers].detect{|v| args.include?(v)}
56
+ @setters[name] = t ? [*args[t]] : ["#{name}=".to_sym]
57
+
58
+ # nil or a after callback
59
+ t = [:after_get, :after_getter, :after_read, :after_reader,
60
+ :after, :after_callback, :callback].detect{|v| args[v]}
61
+ @after_get[name] = t ? args[t] : nil
62
+ ## @after_get[name] = block if block # and t.nil?
63
+ # NOTE: the line above doesn't work when default == {}
64
+ # since it assumes there is a block
65
+
66
+ @type_raw[name] =
67
+ if t = [:bool, :boolean, :binary, :FalseClass,
68
+ :TrueClass].detect{|v| args.include?(v)}
69
+ [:TrueClass, args[t]]
70
+ elsif t = [:int, :integer, :fixnum, :Fixnum,
71
+ Fixnum].detect{|v| args.include?(v)}
72
+ [:Fixnum, args[t]]
73
+ elsif t = [:float, :Float, Float].detect{|v| args.include?(v)}
74
+ [:Float, args[t]]
75
+ elsif t = [:string, :str, :String, String].detect{|v| args.include?(v)}
76
+ [:String, args[t]]
77
+ elsif t = [:dict, :hash, :Hash, Hash].detect{|v| args.include?(v)}
78
+ [:Hash, args[t]]
79
+ elsif t = [:array, :list, :Array, Array].detect{|v| args.include?(v)}
80
+ [:Array, args[t]]
81
+ elsif t = [:set, :Set, Set].detect{|v| args.include?(v)}
82
+ [:Set, args[t]]
83
+ elsif t = [:proc, :Proc, Proc].detect{|v| args.include?(v)}
84
+ [:Proc, args[t]]
85
+ ## elsif t = [:nil, :NilClass, NilClass].detect{|v| args.include?(v)}
86
+ ## [:NilClass, args[t]]
87
+ elsif t = [:sym, :symbol, :Symbol, Symbol].detect{|v| args.include?(v)}
88
+ [:Symbol, args[t]]
89
+ else
90
+ args.include?(:type) ? [args[:type].to_s.to_sym, true] : [nil, nil]
91
+ end
92
+
93
+ if t = [:blank, :null].detect{|v| args.include?(v)}
94
+ @blank[name] = if Proc === args[t]
95
+ args[t]
96
+ else
97
+ blank_val = args[t]
98
+ Proc.new{|v| v == blank_val}
99
+ end
100
+ (@options_given[name] ||= []) << :blank
64
101
  end
65
102
 
66
- @getters[name] = if args.include? :getter
67
- [*args[:getter]]
68
- elsif args.include? :getters
69
- [*args[:getters]]
70
- elsif args.include? :get
71
- [*args[:get]]
72
- else
73
- [name.to_sym]
103
+ t = [:proc_inst_exec, :proc_instance_exec].detect{|v| args.include?(v)}
104
+ @proc_inst_exec[name] = t ? !!args[t] : CascadingClasses.proc_inst_exec?
105
+
106
+ t = [:block_inst_exec, :block_instance_exec].detect{|v| args.include?(v)}
107
+ @block_inst_exec[name] = t ? !!args[t] : CascadingClasses.block_inst_exec?
108
+
109
+ t = [:new, :new_instance, :init, :initialize].detect{|v| args.include?(v)}
110
+ if t
111
+ raise "#{args[t]} is not of type Proc (for #{t})" unless Proc === args[t]
112
+ @new[name] = args[t]
113
+ (@options_given[name] ||= []) << :new
114
+ end
115
+
116
+ t = [:ensure_type].detect{|v| args.include?(v)}
117
+ if t
118
+ raise "#{args[t]} is not of type Proc (for #{t})" unless Proc === args[t]
119
+ @ensure_type[name] = args[t]
120
+ (@options_given[name] ||= []) << :ensure_type
74
121
  end
75
122
 
76
- @setters[name] = if args.include? :setter
77
- [*args[:setter]]
78
- elsif args.include? :setters
79
- [*args[:setters]]
80
- elsif args.include? :set
81
- [*args[:set]]
82
- else
83
- ["#{name}=".to_sym]
123
+ t = [:inherit, :inherit_mode].detect{|v| args.include?(v)}
124
+ if t
125
+ unless s = [nil, false, true].any?{|v| args[t] == v}
126
+ raise "':inherit' value: #{args[t]} must be one of: nil, false, true, <fixnum>" unless
127
+ Fixnum === args[t]
128
+ end
129
+ @inherit[name] = args[t]
130
+ (@options_given[name] ||= []) << :inherit
84
131
  end
85
132
  end
86
133
 
87
134
  def initialize(apply_to_instances, &block)
88
- @apply_to_instances = apply_to_instances
135
+ @type_raw = {} # temp var
136
+ @apply_to_instances = !!apply_to_instances
89
137
  @names = []
90
- @defaults, @instances_too = {}, {}
138
+ @default, @instances_too = {}, {}
91
139
  @getters, @setters = {}, {}
140
+ @type, @after_get = {}, {}
141
+ @blank, @proc_inst_exec, @block_inst_exec = {}, {}, {}
142
+ @new, @inherit = {}, {}
143
+ @ensure_type = {}
144
+ @options_given = {} # need to know which options were set manually
92
145
 
93
146
  return unless block_given?
94
147
  if block.arity == 0
@@ -96,6 +149,31 @@ module CascadingClasses
96
149
  else
97
150
  raise "ArgumentError: wrong number of args for block: #{block.arity} for 1"
98
151
  end
152
+
153
+ @names.each do |name|
154
+ type = @type[name] = CascadingClasses.get_type(@default[name], *@type_raw[name])
155
+
156
+ @blank[name] = CascadingClasses.default_blank(type) unless @blank[name]
157
+ @new[name] = CascadingClasses.default_new(type) unless @new[name]
158
+
159
+ unless CascadingClasses.undefined_type?(type)
160
+ ## unless @default.include?(name) and not @default[name].nil?
161
+ unless @default.include?(name)
162
+ @default[name] = @new[name].call
163
+ end
164
+ end
165
+
166
+ unless @inherit.include?(name)
167
+ @inherit[name] = CascadingClasses.undefined_type?(type) ? :undef :
168
+ (CascadingClasses.is_container?(type) ? false : true)
169
+ end
170
+
171
+ @ensure_type[name] =
172
+ CascadingClasses.default_ensure_type(type, @blank[name], @new[name]) unless
173
+ @ensure_type.include?(name)
174
+
175
+ @options_given[name] ||= []
176
+ end
99
177
  end
100
178
 
101
179
  end # class Helper
@@ -0,0 +1,71 @@
1
+ require 'cascading_classes/dsl'
2
+
3
+ module CascadingClasses
4
+
5
+ # returns hash: {..., attr => [default, inst_too], .. }
6
+ # todo: consider removing support for custom getters/setters in array syntax
7
+ def self.parse_options(apply_to_instances, *properties, &block)
8
+ err = "syntax error: list properties or supply block, but not both"
9
+ raise err if properties.size > 0 and block_given?
10
+
11
+ res = {}
12
+ properties.each do |prop|
13
+ name, default, inst_too = prop, nil, apply_to_instances
14
+ if Array === prop
15
+ case prop.size
16
+ when 0 then next
17
+ when 1 then name = prop[0]
18
+ when 2 then name, default = prop[0..1]
19
+ else
20
+ name, default, inst_too = prop[0..2]
21
+ end
22
+ end
23
+ name = name.to_sym
24
+ ## type = default.nil? ? :Object : default.class.name.to_sym
25
+ type = default.nil? ? CascadingClasses.undefined_type() : default.class.name.to_sym
26
+
27
+ new_proc = CascadingClasses.default_new(type) # proc creates new obj
28
+ is_blank = CascadingClasses.default_blank(type) # proc verifies blank values
29
+
30
+ res[name] = {
31
+ :default => default, :inst_too => !!inst_too,
32
+ :getters => [name], :setters => ["#{name}=".to_sym],
33
+ :type => type, :after_get => nil,
34
+ :blank => is_blank,
35
+ :proc_inst_exec => CascadingClasses.proc_inst_exec?,
36
+ :block_inst_exec => CascadingClasses.block_inst_exec?,
37
+ :new => new_proc,
38
+ :inherit => CascadingClasses.undefined_type?(type) ?
39
+ :undef : (CascadingClasses.is_container?(type) ? false : true),
40
+ :ensure_type => CascadingClasses.default_ensure_type(type, is_blank, new_proc),
41
+ :options_given => []
42
+ }
43
+ end
44
+
45
+ if block_given?
46
+ a = CascadingClasses::DSL.new(apply_to_instances, &block)
47
+
48
+ names = a.instance_eval{ @names }
49
+
50
+ pluck = proc{|v| a.instance_eval("@#{v}") }
51
+
52
+ vars = [:default, :instances_too, :getters, :setters, :type, :after_get, :blank,
53
+ :proc_inst_exec, :block_inst_exec, :new, :inherit, :ensure_type,
54
+ :options_given]
55
+
56
+ names.each{|name| res[name] = Hash[ vars.map{|v| [v, pluck.call(v)[name]] } ] }
57
+ end
58
+
59
+ # validation
60
+ res.each do |name, settings|
61
+
62
+ =begin -> why must new values be blank? ans: b/c if they don't
63
+ new_var = settings[:new].call
64
+ raise "proc to create new #{settings[:type]} is #{new_var.inspect}, not blank" unless
65
+ settings[:blank].call(new_var)
66
+ =end
67
+ end
68
+
69
+ res
70
+ end
71
+ end
@@ -0,0 +1,229 @@
1
+ module CascadingClasses
2
+ def self.undefined_type
3
+ :undefined_type
4
+ end
5
+
6
+ def self.undefined_type?(type)
7
+ type == self.undefined_type
8
+ end
9
+
10
+ def self.basic_types
11
+ [:String, :Fixnum, :Float, :bool, :boolean, :Symbol, :Hash, :Array,
12
+ :Set, :Proc, :FalseClass, :TrueClass]
13
+ end
14
+
15
+ # turns :Hash into Hash, :Array into Array, ...
16
+ def self.unsymbolize(obj)
17
+ res = eval(obj.to_s) rescue nil
18
+ (res.class == Class) ? res : obj
19
+ end
20
+
21
+ # typically you will be given just a default value, from that
22
+ # we need to determine the type: default.class.to_s.to_sym
23
+ # in this case ensure_type will be nil and ensure_val will be ignored
24
+ #
25
+ # but a type can also be specified explicitly:
26
+ #
27
+ # A.cascade do
28
+ # name :hash => true
29
+ # end
30
+ #
31
+ # in this case ensure_type will be :Hash and ensure_val will be true
32
+ def self.get_type(default=nil, ensure_type=nil, ensure_val=nil)
33
+ raise "type: #{ensure_type} must be set to true or false, not #{ensure_val}" unless
34
+ ensure_val == true or ensure_val == false if ensure_type
35
+
36
+ type = default.class.to_s.to_sym
37
+ type = self.undefined_type if type == :NilClass
38
+
39
+ if ensure_type
40
+ if ensure_val then ensure_type
41
+ ## elsif ensure_type == type then :Object
42
+ elsif ensure_type == type then self.undefined_type
43
+ else type
44
+ end
45
+ else type
46
+ end
47
+ end
48
+
49
+ def self.is_of_type?(obj, klass)
50
+ obj.class.to_s.to_sym == klass
51
+ end
52
+
53
+ # returns a proc that takes an argument, evaluating
54
+ # whether it is blank
55
+ def self.default_blank(klass)
56
+ ## raise "Hell" unless basic_type.include klass # sanity
57
+
58
+ =begin
59
+ if is_container?(klass)
60
+ return Proc.new{|v| v.respond_to?(:size) ? v.size.zero? : true} # *eval*
61
+ end
62
+ =end
63
+ return Proc.new{|v| v.size.zero?} if is_container?(klass) # *eval*
64
+
65
+ klass = klass.to_s.to_sym
66
+
67
+ case klass
68
+ ## when :Proc, :Object then Proc.new{|v| v.nil? }
69
+ when :Proc then Proc.new{|v| v.nil? }
70
+ when :String then Proc.new{|v| v == ''}
71
+ when :Fixnum, :Float then Proc.new{|v| v.nil?}
72
+ ## Proc.new{|v| v.zero? }
73
+ when :Symbol then Proc.new{|v| v.nil? or v.size.zero? }
74
+ when :FalseClass, :TrueClass then Proc.new{|v| v.nil? }
75
+ else
76
+ self.undefined_type?(klass) ? Proc.new{|v| v.nil? } :
77
+ Proc.new{|v| !!v}
78
+ end
79
+ end
80
+
81
+ # subclass' initialition of own version of property
82
+ def self.default_new(klass)
83
+ ## raise "Hell" unless basic_type.include klass # sanity
84
+ case klass
85
+ when :String then Proc.new{ String.new }
86
+ when :Fixnum then Proc.new{ nil }
87
+ ## Proc.new{ 0 }
88
+ when :Float then Proc.new{ nil }
89
+ ## Proc.new{ 0.0 }
90
+ when :Hash then Proc.new{ Hash.new }
91
+ when :Array then Proc.new{ Array.new }
92
+ when :Proc, :Symbol then Proc.new{ nil }
93
+ when :FalseClass, :TrueClass, :NilClass then Proc.new{ nil }
94
+ else
95
+ if self.undefined_type?(klass) then Proc.new{ nil }
96
+ else
97
+ s = (klass.class == Class) ? klass : unsymbolize(klass) # *eval*
98
+ if s.class == Class and s.respond_to?(:new)
99
+ Proc.new{ s.new }
100
+ else
101
+ raise "don't know how to create new instance for #{klass}"
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # naive transformation to be applied for each type to ensure
108
+ # that it stays that type
109
+ # of course it would be better (wouldn't it??) to react
110
+ # to an object rather than a 'type'
111
+ # but one of the features we support is to specify the
112
+ # type before any default data has been given
113
+ # procs take any value and should attempt to convert to specified type
114
+ # is_blank: proc that determines whether a value of that type is blank
115
+ # new_proc: proc that creates new objects of that type
116
+ def self.default_ensure_type(klass, is_blank, new_proc)
117
+ ## raise "Hell" unless basic_type.include klass # sanity
118
+ klass = klass.to_s.to_sym
119
+ pre = lambda{|v|
120
+ is_blank.call(v) ? v : (v || new_proc.call) }
121
+
122
+ case klass
123
+ when :Hash
124
+ lambda do |v|
125
+ return v if is_blank.call(v)
126
+ v = new_proc.call if v.nil?
127
+ return v if CascadingClasses.is_of_type?(v, klass)
128
+ v = v.to_hash if v.respond_to?(:to_hash)
129
+ Hash === v ? v : {v => nil}
130
+ end
131
+ when :Array
132
+ lambda do |v|
133
+ return v if is_blank.call(v)
134
+ v = new_proc.call if v.nil?
135
+ return v if CascadingClasses.is_of_type?(v, klass)
136
+ v = v.to_a if v.respond_to?(:to_a)
137
+ Array === v ? v : [*v]
138
+ end
139
+ when :Set
140
+ lambda do |v|
141
+ return v if is_blank.call(v)
142
+ v = new_proc.call if v.nil?
143
+ return v if CascadingClasses.is_of_type?(v, klass)
144
+ if v.respond_to? :to_set
145
+ v.to_set
146
+ elsif v.respond_to? :to_a
147
+ Set.new(v.to_a)
148
+ else
149
+ Set.new([*v])
150
+ end
151
+ end
152
+ ## when :Proc then Proc.new{|v| v}
153
+ when :Proc
154
+ Proc.new do |v|
155
+ if v.nil? then nil
156
+ elsif Proc === v then v
157
+ else Proc.new{ v }
158
+ end
159
+ end
160
+ when :Fixnum
161
+ lambda{|v|
162
+ return v if is_blank.call(v)
163
+ v.to_i
164
+ }
165
+ when :Float
166
+ lambda{|v|
167
+ return v if is_blank.call(v)
168
+ v.to_f
169
+ }
170
+ when :String
171
+ lambda{|v|
172
+ return v if is_blank.call(v)
173
+ v.to_s
174
+ }
175
+ when :Symbol
176
+ lambda{|v|
177
+ return v if is_blank.call(v)
178
+ v.respond_to?(:to_sym) ? v.to_sym : "#{v}".to_sym
179
+ }
180
+ when :FalseClass, :TrueClass
181
+ lambda{|v|
182
+ return v if is_blank.call(v)
183
+ v.nil? ? nil : !!(v)
184
+ }
185
+ when :NilClass then Proc.new{|v| nil}
186
+ ## when :Object then Proc.new{|v| v} # unknown type
187
+ ## Proc.new{|v| raise "hell"} # sanity
188
+ else
189
+ if self.undefined_type?(klass) then Proc.new{|v| v}
190
+ else
191
+ lambda do |v|
192
+ return v if is_blank.call(v)
193
+ v = new_proc.call if v.nil?
194
+ return v if CascadingClasses.is_of_type?(v, klass)
195
+ raise "error: don't know how to convert #{v} to object of type #{klass}"
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ def self.is_container?(obj, blk=nil)
202
+ obj = unsymbolize(obj) if Symbol === obj
203
+ return blk.call(obj) if blk
204
+ s = (obj.class == Class) ? obj : obj.class
205
+ s.ancestors.include?(Enumerable) or
206
+ s.instance_methods.include?(:each)
207
+ end
208
+
209
+ def self.is_hash_like?(obj, blk=nil)
210
+ obj = unsymbolize(obj) if Symbol === obj
211
+ return blk.call(obj) if blk
212
+ s = (obj.class == Class) ? obj : obj.class
213
+ return true if s.ancestors.include?(Hash)
214
+ meths = s.instance_methods
215
+ meths.include?(:keys) and meths.include?(:include?) and
216
+ (meths.include?(:[]) or meths.include?(:fetch))
217
+ end
218
+
219
+ def self.is_array_like?(obj, blk=nil)
220
+ obj = unsymbolize(obj) if Symbol === obj
221
+ return blk.call(obj) if blk
222
+ s = (obj.class == Class) ? obj : obj.class
223
+ return true if s.ancestors.include?(Array)
224
+ meths = s.instance_methods
225
+ meths.include?(:index) and meths.include?(:at) and
226
+ meths.include?(:include?)
227
+ end
228
+
229
+ end