cascading_classes 0.3.0 → 0.6.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.
@@ -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