doodle 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +24 -0
- data/Manifest.txt +26 -1
- data/README.txt +9 -8
- data/lib/doodle.rb +43 -1496
- data/lib/doodle/app.rb +6 -0
- data/lib/doodle/attribute.rb +165 -0
- data/lib/doodle/base.rb +180 -0
- data/lib/doodle/collector-1.9.rb +72 -0
- data/lib/doodle/collector.rb +191 -0
- data/lib/doodle/comparable.rb +8 -0
- data/lib/doodle/conversion.rb +80 -0
- data/lib/doodle/core.rb +42 -0
- data/lib/doodle/datatype-holder.rb +39 -0
- data/lib/doodle/debug.rb +20 -0
- data/lib/doodle/deferred.rb +13 -0
- data/lib/doodle/equality.rb +21 -0
- data/lib/doodle/exceptions.rb +29 -0
- data/lib/doodle/factory.rb +91 -0
- data/lib/doodle/getter-setter.rb +154 -0
- data/lib/doodle/info.rb +298 -0
- data/lib/doodle/inherit.rb +40 -0
- data/lib/doodle/json.rb +38 -0
- data/lib/doodle/marshal.rb +16 -0
- data/lib/doodle/normalized_array.rb +512 -0
- data/lib/doodle/normalized_hash.rb +356 -0
- data/lib/doodle/ordered-hash.rb +8 -0
- data/lib/doodle/singleton.rb +23 -0
- data/lib/doodle/smoke-and-mirrors.rb +23 -0
- data/lib/doodle/to_hash.rb +17 -0
- data/lib/doodle/utils.rb +173 -11
- data/lib/doodle/validation.rb +122 -0
- data/lib/doodle/version.rb +1 -1
- data/lib/molic_orderedhash.rb +24 -10
- data/spec/assigned_spec.rb +45 -0
- data/spec/attributes_spec.rb +7 -7
- data/spec/collector_spec.rb +100 -13
- data/spec/doodle_context_spec.rb +5 -5
- data/spec/from_spec.rb +43 -3
- data/spec/json_spec.rb +232 -0
- data/spec/member_init_spec.rb +11 -11
- data/spec/modules_spec.rb +4 -4
- data/spec/multi_collector_spec.rb +91 -0
- data/spec/must_spec.rb +32 -0
- data/spec/spec_helper.rb +14 -4
- data/spec/specialized_attribute_class_spec.rb +2 -2
- data/spec/typed_collector_spec.rb +57 -0
- data/spec/xml_spec.rb +8 -8
- metadata +33 -3
data/lib/doodle/app.rb
CHANGED
@@ -67,6 +67,7 @@ class Doodle
|
|
67
67
|
class Filename < Option
|
68
68
|
doodle do
|
69
69
|
boolean :existing, :default => false, :doc => "set to true if file must exist"
|
70
|
+
boolean :expand, :default => false, :doc => "set to true if you want to have the filename expanded"
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
@@ -176,6 +177,11 @@ class Doodle
|
|
176
177
|
File.exist?(s)
|
177
178
|
end
|
178
179
|
end
|
180
|
+
if da.expand
|
181
|
+
from String do |s|
|
182
|
+
File.expand_path(s)
|
183
|
+
end
|
184
|
+
end
|
179
185
|
end
|
180
186
|
end
|
181
187
|
# expect an on/off flag, e.g. -b
|
@@ -0,0 +1,165 @@
|
|
1
|
+
class Doodle
|
2
|
+
# Attribute is itself a Doodle object that is created by #has and
|
3
|
+
# added to the #attributes collection in an object's DoodleInfo
|
4
|
+
#
|
5
|
+
# It is used to provide a context for defining #must and #from rules
|
6
|
+
#
|
7
|
+
class DoodleAttribute < Doodle
|
8
|
+
# note: using extend with a module causes an infinite loop in 1.9
|
9
|
+
# hence the inline
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# rewrite rules for the argument list to #has
|
13
|
+
def params_from_args(owner, *args)
|
14
|
+
Doodle::Debug.d { [owner, args] }
|
15
|
+
key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
|
16
|
+
params = { }
|
17
|
+
if positional_args.size > 0
|
18
|
+
name = positional_args.shift
|
19
|
+
case name
|
20
|
+
# has Person --> has :person, :kind => Person
|
21
|
+
when Class
|
22
|
+
params[:name] = Utils.snake_case(name.to_s.split(/::/).last)
|
23
|
+
params[:kind] = name
|
24
|
+
else
|
25
|
+
params[:name] = name.to_s.to_sym
|
26
|
+
end
|
27
|
+
end
|
28
|
+
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
29
|
+
#DBG: Doodle::Debug.d { [:has, self, self.class, params] }
|
30
|
+
if !params.key?(:name)
|
31
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} must have a name", Doodle::Utils.doodle_caller
|
32
|
+
params[:name] = :__ERROR_missing_name__
|
33
|
+
else
|
34
|
+
# ensure that :name is a symbol
|
35
|
+
params[:name] = params[:name].to_sym
|
36
|
+
end
|
37
|
+
name = params[:name]
|
38
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments", Doodle::Utils.doodle_caller if positional_args.size > 0
|
39
|
+
|
40
|
+
if collector = params.delete(:collect)
|
41
|
+
if !params.key?(:using)
|
42
|
+
if params.key?(:key)
|
43
|
+
params[:using] = KeyedAttribute
|
44
|
+
else
|
45
|
+
params[:using] = AppendableAttribute
|
46
|
+
end
|
47
|
+
end
|
48
|
+
# this in generic CollectorAttribute class
|
49
|
+
# collector from(Hash)
|
50
|
+
|
51
|
+
# TODO: rework this to allow multiple classes and mappings
|
52
|
+
#p [:collector, collector, params, params[:init].kind_of?(Class)]
|
53
|
+
# FIXME: collector
|
54
|
+
if collector.kind_of?(Hash)
|
55
|
+
collector_spec = collector
|
56
|
+
#collector_name, collector_class = collector.to_a[0]
|
57
|
+
elsif collector.kind_of?(Array) && collector.all? { |i| i.kind_of?(Hash) }
|
58
|
+
collector_spec = collector.inject(OrderedHash.new) { |hash, item|
|
59
|
+
item.keys.each do |key|
|
60
|
+
hash[key] = item[key]
|
61
|
+
end
|
62
|
+
hash
|
63
|
+
}
|
64
|
+
else
|
65
|
+
collectors = [collector].flatten
|
66
|
+
collector_spec = collectors.inject(OrderedHash.new) do |hash, klass|
|
67
|
+
collector_class = klass.to_s
|
68
|
+
#p [:collector_klass, collector_klass]
|
69
|
+
collector_name = Utils.snake_case(collector_class.split(/::/).last)
|
70
|
+
#p [:collector_name, collector_class, collector_name]
|
71
|
+
# FIXME: sanitize class name for 1.9 (make this a Utils function)
|
72
|
+
collector_class = collector_class.gsub(/#<Class:0x[a-fA-F0-9]+>::/, '')
|
73
|
+
# if Capitalized word given, treat as classname and create
|
74
|
+
# collector for specific class
|
75
|
+
if collector_class !~ /^[A-Z]/
|
76
|
+
collector_class = nil
|
77
|
+
end
|
78
|
+
hash[collector_name] = collector_class
|
79
|
+
hash
|
80
|
+
end
|
81
|
+
#!p [:collector_klass, collector_klass, params[:init]]
|
82
|
+
end
|
83
|
+
params[:collector_spec] = collector_spec
|
84
|
+
end
|
85
|
+
params[:doodle_owner] = owner
|
86
|
+
#p [:params, owner, params]
|
87
|
+
params
|
88
|
+
end
|
89
|
+
end
|
90
|
+
extend ClassMethods
|
91
|
+
|
92
|
+
# must define these methods before using them in #has below
|
93
|
+
|
94
|
+
# hack: bump off +validate!+ for Attributes - maybe better way of doing
|
95
|
+
# this however, without this, tries to validate Attribute to :kind
|
96
|
+
# specified, e.g. if you have
|
97
|
+
#
|
98
|
+
# has :date, :kind => Date
|
99
|
+
#
|
100
|
+
# it will fail because Attribute is not a kind of Date -
|
101
|
+
# obviously, I have to think about this some more :S
|
102
|
+
#
|
103
|
+
# at least, I could hand roll a custom validate! method for Attribute
|
104
|
+
#
|
105
|
+
def validate!(all = true)
|
106
|
+
end
|
107
|
+
|
108
|
+
# has default been defined?
|
109
|
+
def default_defined?
|
110
|
+
ivar_defined?(:default)
|
111
|
+
end
|
112
|
+
|
113
|
+
# has default been defined?
|
114
|
+
def init_defined?
|
115
|
+
ivar_defined?(:init)
|
116
|
+
end
|
117
|
+
|
118
|
+
# is this attribute optional? true if it has a default defined for it
|
119
|
+
def optional?
|
120
|
+
default_defined? or init_defined?
|
121
|
+
end
|
122
|
+
|
123
|
+
# an attribute is required if it has no default or initial value defined for it
|
124
|
+
def required?
|
125
|
+
# d { [:default?, self.class, self.name, instance_variable_defined?("@default"), @default] }
|
126
|
+
!optional?
|
127
|
+
end
|
128
|
+
|
129
|
+
# special case - not an attribute
|
130
|
+
define_getter_setter :doodle_owner
|
131
|
+
|
132
|
+
# temporarily fake existence of abstract attribute - later has
|
133
|
+
# :abstract overrides this
|
134
|
+
def abstract
|
135
|
+
@abstract = false
|
136
|
+
end
|
137
|
+
|
138
|
+
# temporarily fake existence of readonly attribute
|
139
|
+
def readonly
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
# name of attribute
|
144
|
+
has :name, :kind => Symbol do
|
145
|
+
from String do |s|
|
146
|
+
s.to_sym
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# default value (can be a block)
|
151
|
+
has :default, :default => nil
|
152
|
+
|
153
|
+
# initial value
|
154
|
+
has :init, :default => nil
|
155
|
+
|
156
|
+
# documentation
|
157
|
+
has :doc, :default => ""
|
158
|
+
|
159
|
+
# don't try to initialize from this class
|
160
|
+
remove_method(:abstract) # because we faked it earlier - remove to avoid redefinition warning
|
161
|
+
has :abstract, :default => false
|
162
|
+
remove_method(:readonly) # because we faked it earlier - remove to avoid redefinition warning
|
163
|
+
has :readonly, :default => false
|
164
|
+
end
|
165
|
+
end
|
data/lib/doodle/base.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
class Doodle
|
2
|
+
# the core module of Doodle - however, to get most facilities
|
3
|
+
# provided by Doodle without inheriting from Doodle, include
|
4
|
+
# Doodle::Core, not this module
|
5
|
+
module BaseMethods
|
6
|
+
include Singleton
|
7
|
+
include SmokeAndMirrors
|
8
|
+
include ToHash
|
9
|
+
include ModMarshal
|
10
|
+
include GetterSetter
|
11
|
+
include ValidationHelper
|
12
|
+
include ConversionHelper
|
13
|
+
|
14
|
+
# NOTE: can't do either of these
|
15
|
+
|
16
|
+
# include Equality
|
17
|
+
# include Comparable
|
18
|
+
|
19
|
+
# def self.included(other)
|
20
|
+
# other.module_eval {
|
21
|
+
# include Equality
|
22
|
+
# include Comparable
|
23
|
+
# }
|
24
|
+
# end
|
25
|
+
|
26
|
+
# this is the only way to get at internal values. Note: this is
|
27
|
+
# initialized on the fly rather than in #initialize because
|
28
|
+
# classes and singletons don't call #initialize
|
29
|
+
def __doodle__
|
30
|
+
@__doodle__ ||= DoodleInfo.new(self)
|
31
|
+
end
|
32
|
+
protected :__doodle__
|
33
|
+
|
34
|
+
# set up global datatypes
|
35
|
+
def datatypes(*mods)
|
36
|
+
mods.each do |mod|
|
37
|
+
DataTypeHolder.class_eval { include mod }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# vector through this method to get to doodle info or enable global
|
42
|
+
# datatypes and provide an interface that allows you to add your own
|
43
|
+
# datatypes to this declaration
|
44
|
+
def doodle(*mods, &block)
|
45
|
+
if mods.size == 0 && !block_given?
|
46
|
+
__doodle__
|
47
|
+
else
|
48
|
+
dh = Doodle::DataTypeHolder.new(self)
|
49
|
+
mods.each do |mod|
|
50
|
+
dh.extend(mod)
|
51
|
+
end
|
52
|
+
dh.instance_eval(&block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# +doc+ add docs to doodle class or attribute
|
57
|
+
def doc(*args, &block)
|
58
|
+
if args.size > 0
|
59
|
+
@doc = *args
|
60
|
+
else
|
61
|
+
@doc
|
62
|
+
end
|
63
|
+
end
|
64
|
+
alias :doc= :doc
|
65
|
+
|
66
|
+
# +has+ is an extended +attr_accessor+
|
67
|
+
#
|
68
|
+
# simple usage - just like +attr_accessor+:
|
69
|
+
#
|
70
|
+
# class Event
|
71
|
+
# has :date
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# set default value:
|
75
|
+
#
|
76
|
+
# class Event
|
77
|
+
# has :date, :default => Date.today
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# set lazily evaluated default value:
|
81
|
+
#
|
82
|
+
# class Event
|
83
|
+
# has :date do
|
84
|
+
# default { Date.today }
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
def has(*args, &block)
|
89
|
+
Doodle::Debug.d { [:args, self, self.class, args] }
|
90
|
+
params = DoodleAttribute.params_from_args(self, *args)
|
91
|
+
Doodle::Debug.d { [:params, self, params] }
|
92
|
+
# get specialized attribute class or use default
|
93
|
+
attribute_class = params.delete(:using) || DoodleAttribute
|
94
|
+
|
95
|
+
# could this be handled in DoodleAttribute?
|
96
|
+
# define getter setter before setting up attribute
|
97
|
+
define_getter_setter params[:name], params, &block
|
98
|
+
#p [:attribute, attribute_class, params]
|
99
|
+
attr = __doodle__.local_attributes[params[:name]] = attribute_class.new(params, &block)
|
100
|
+
|
101
|
+
# FIXME: not sure this is really the right place for this (but
|
102
|
+
# right now the only place I can get it to work :)
|
103
|
+
if from_defined = params[:from]
|
104
|
+
from_defined.each do |k, v|
|
105
|
+
Doodle::Debug.d { [:defining, self, k, v]}
|
106
|
+
attr.instance_eval { from k, &v }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if must_defined = params[:must]
|
111
|
+
must_defined.each do |k, v|
|
112
|
+
attr.instance_eval { must k, &v }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
attr
|
117
|
+
end
|
118
|
+
|
119
|
+
# define order for positional arguments
|
120
|
+
def arg_order(*args)
|
121
|
+
if args.size > 0
|
122
|
+
begin
|
123
|
+
args = args.uniq
|
124
|
+
args.each do |x|
|
125
|
+
__doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol", Doodle::Utils.doodle_caller if !(x.class <= Symbol)
|
126
|
+
__doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", Doodle::Utils.doodle_caller if !doodle.attributes.keys.include?(x)
|
127
|
+
end
|
128
|
+
__doodle__.arg_order = args
|
129
|
+
rescue Exception => e
|
130
|
+
__doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, Doodle::Utils.doodle_caller
|
131
|
+
end
|
132
|
+
else
|
133
|
+
__doodle__.arg_order + (__doodle__.attributes.keys - __doodle__.arg_order)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# return true if instance variable +name+ defined
|
138
|
+
# FIXME: move
|
139
|
+
def ivar_defined?(name)
|
140
|
+
instance_variable_defined?("@#{name}")
|
141
|
+
end
|
142
|
+
private :ivar_defined?
|
143
|
+
|
144
|
+
# get an instance variable by symbolic name
|
145
|
+
def ivar_get(name)
|
146
|
+
instance_variable_get("@#{name}")
|
147
|
+
end
|
148
|
+
|
149
|
+
# return true if attribute has default defined and not yet been
|
150
|
+
# assigned to (i.e. still has default value)
|
151
|
+
def default?(name)
|
152
|
+
# FIXME: should this be in DoodleInfo or here?
|
153
|
+
__doodle__.attributes[name.to_sym].optional? && !ivar_defined?(name)
|
154
|
+
end
|
155
|
+
|
156
|
+
# return true if attribute has been assigned to
|
157
|
+
def assigned?(name)
|
158
|
+
ivar_defined?(name)
|
159
|
+
end
|
160
|
+
|
161
|
+
# object can be initialized from a mixture of positional arguments,
|
162
|
+
# hash of keyword value pairs and a block which is instance_eval'd
|
163
|
+
def initialize(*args, &block)
|
164
|
+
built_in = Doodle::BuiltIns::BUILTINS.select{ |x| self.kind_of?(x) }.first
|
165
|
+
if built_in
|
166
|
+
super
|
167
|
+
end
|
168
|
+
__doodle__.validation_on = true
|
169
|
+
#p [:doodle_parent, Doodle.parent, caller[-1]]
|
170
|
+
Doodle.context.push(self)
|
171
|
+
__doodle__.defer_validation do
|
172
|
+
__doodle__.update(*args, &block)
|
173
|
+
end
|
174
|
+
Doodle.context.pop
|
175
|
+
#p [:doodle, __doodle__.__inspect__]
|
176
|
+
#p [:doodle, __doodle__.attributes]
|
177
|
+
#p [:doodle_parent, __doodle__.parent]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# 1.8.7+ versions
|
2
|
+
class Doodle
|
3
|
+
class AppendableAttribute
|
4
|
+
def define_collector
|
5
|
+
# FIXME: don't use eval in 1.9+
|
6
|
+
#name = self.name
|
7
|
+
#collector_name = self.collector_name
|
8
|
+
#collector_class = self.collector_class
|
9
|
+
this = self
|
10
|
+
if collector_class.nil?
|
11
|
+
doodle_owner.sc_eval do
|
12
|
+
define_method this.collector_name do |*args, &block|
|
13
|
+
collection = send(this.name)
|
14
|
+
#p [this.collector_name, 1, this.name, args]
|
15
|
+
# unshift the block onto args so not consumed by <<
|
16
|
+
#args.unshift(block) if block_given?
|
17
|
+
collection.<<(*args, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
doodle_owner.sc_eval do
|
22
|
+
define_method this.collector_name do |*args, &block|
|
23
|
+
collection = send(this.name)
|
24
|
+
#p [this.collector_name, 1, this.name, args]
|
25
|
+
#args.unshift(block) if block_given?
|
26
|
+
if args.size > 0 and args.all?{|x| x.kind_of?(this.collector_class)}
|
27
|
+
collection.<<(*args, &block)
|
28
|
+
else
|
29
|
+
collection << this.collector_class.new(*args, &block)
|
30
|
+
#collection.<<(*args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class KeyedAttribute
|
39
|
+
def define_collector
|
40
|
+
# save ref to self for use in closure
|
41
|
+
this = self
|
42
|
+
if this.collector_class.nil?
|
43
|
+
doodle_owner.sc_eval do
|
44
|
+
#p [:defining, this.collector_name]
|
45
|
+
define_method this.collector_name do |*args, &block|
|
46
|
+
#p [this.collector_name, 1, args]
|
47
|
+
collection = send(this.name)
|
48
|
+
args.each do |arg|
|
49
|
+
collection[arg.send(key)] = arg
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
doodle_owner.sc_eval do
|
55
|
+
#p [:defining, this.collector_name]
|
56
|
+
define_method this.collector_name do |*args, &block|
|
57
|
+
#p [this.collector_name, 2, args]
|
58
|
+
collection = send(this.name)
|
59
|
+
if args.size > 0 and args.all?{|x| x.kind_of?(this.collector_class)}
|
60
|
+
args.each do |arg|
|
61
|
+
collection[arg.send(this.key)] = arg
|
62
|
+
end
|
63
|
+
else
|
64
|
+
obj = this.collector_class.new(*args, &block)
|
65
|
+
collection[obj.send(this.key)] = obj
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
class Doodle
|
2
|
+
|
3
|
+
def self.TypedArray(*klasses)
|
4
|
+
typed_class = Class.new(NormalizedArray) do
|
5
|
+
define_method :normalize_value do |v|
|
6
|
+
if !klasses.any?{ |klass| v.kind_of?(klass) }
|
7
|
+
raise TypeError, "#{self.class}: #{v.class}(#{v.inspect}) is not a kind of #{klasses.map{ |c| c.to_s }.join(', ')}", [caller[-1]]
|
8
|
+
end
|
9
|
+
v
|
10
|
+
end
|
11
|
+
end
|
12
|
+
#p [:creating_class, typed_class]
|
13
|
+
typed_class
|
14
|
+
end
|
15
|
+
|
16
|
+
# base class for attribute collector classes
|
17
|
+
class AttributeCollector < DoodleAttribute
|
18
|
+
# FIXME: collector
|
19
|
+
has :collector_spec, :init => { }
|
20
|
+
|
21
|
+
def create_collection
|
22
|
+
if self.init.kind_of?(Class)
|
23
|
+
#p [:create_collection, :class]
|
24
|
+
collection = self.init.new
|
25
|
+
else
|
26
|
+
#p [:create_collection, :clone]
|
27
|
+
collection = self.init.clone
|
28
|
+
end
|
29
|
+
#p [:create_collection, collection]
|
30
|
+
collection
|
31
|
+
end
|
32
|
+
private :create_collection
|
33
|
+
|
34
|
+
def resolve_collector_class
|
35
|
+
# FIXME: collector - perhaps don't allow non-class collectors - should be resolved by this point
|
36
|
+
# perhaps do this in init?
|
37
|
+
collector_spec.each do |k, v|
|
38
|
+
if !v.kind_of?(Class)
|
39
|
+
collector_spec[k] = Doodle::Utils.const_resolve(v)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve_value(value)
|
45
|
+
klasses = collector_spec.values
|
46
|
+
# FIXME: collector - find applicable collector class
|
47
|
+
if klasses.any? { |x| value.kind_of?(x) }
|
48
|
+
# no change required
|
49
|
+
#p [:resolve_value, :value, value]
|
50
|
+
value
|
51
|
+
elsif collector_class = klasses.select { |klass| klass.__doodle__.conversions.key?(value.class) }.first
|
52
|
+
# if the collector_class has a specific conversion for this value class
|
53
|
+
#p [:resolve_value, :collector_class_from, value]
|
54
|
+
collector_class.from(value)
|
55
|
+
else
|
56
|
+
collector_class = klasses.first
|
57
|
+
# try to instantiate collector_class using raw value
|
58
|
+
#p [:resolve_value, :collector_class_new, value]
|
59
|
+
collector_class.new(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(*args, &block)
|
64
|
+
#p [self.class, :initialize]
|
65
|
+
super
|
66
|
+
define_collector
|
67
|
+
from Hash do |hash|
|
68
|
+
# FIXME: collector - my bogon detector just went off the scale - I forget why I have to do this here... :/
|
69
|
+
# oh yes - because I allow forward references using symbols or strings
|
70
|
+
resolve_collector_class
|
71
|
+
collection = create_collection
|
72
|
+
hash.inject(collection) do |h, (key, value)|
|
73
|
+
h[key] = resolve_value(value)
|
74
|
+
h
|
75
|
+
end
|
76
|
+
end
|
77
|
+
from Enumerable do |enum|
|
78
|
+
#p [:enum, Enumerable]
|
79
|
+
# FIXME: collector
|
80
|
+
resolve_collector_class
|
81
|
+
# this is not very elegant but String is a classified as an
|
82
|
+
# Enumerable in 1.8.x (but behaves differently)
|
83
|
+
if enum.kind_of?(String) && self.init.kind_of?(String)
|
84
|
+
post_process( resolve_value(enum) )
|
85
|
+
else
|
86
|
+
post_process( enum.map{ |value| resolve_value(value) } )
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def post_process(results)
|
92
|
+
#p [:post_process, results]
|
93
|
+
collection = create_collection
|
94
|
+
collection.replace(results)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# define collector methods for hash-like attribute collectors
|
99
|
+
class KeyedAttribute < AttributeCollector
|
100
|
+
# has :init, :init => DoodleHash.new
|
101
|
+
has :init, :init => { }
|
102
|
+
#has :init, :init => OrderedHash.new
|
103
|
+
has :key
|
104
|
+
|
105
|
+
def post_process(results)
|
106
|
+
collection = create_collection
|
107
|
+
results.inject(collection) do |h, result|
|
108
|
+
h[result.send(key)] = result
|
109
|
+
h
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if false
|
116
|
+
# not ready for primetime
|
117
|
+
#if RUBY_VERSION >= '1.8.7'
|
118
|
+
# load ruby 1.8.7+ version specific methods
|
119
|
+
require 'doodle/collector-1.9'
|
120
|
+
else
|
121
|
+
# version for ruby 1.8.6
|
122
|
+
class Doodle
|
123
|
+
|
124
|
+
# define collector methods for array-like attribute collectors
|
125
|
+
class AppendableAttribute < AttributeCollector
|
126
|
+
# has :init, :init => DoodleArray.new
|
127
|
+
has :init, :init => []
|
128
|
+
|
129
|
+
# define a collector for appendable collections
|
130
|
+
# - collection should provide a :<< method
|
131
|
+
def define_collector
|
132
|
+
collector_spec.each do |collector_name, collector_class|
|
133
|
+
# FIXME: don't use eval in 1.9+
|
134
|
+
if collector_class.nil?
|
135
|
+
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
136
|
+
collection = self.#{name}
|
137
|
+
args.unshift(block) if block_given?
|
138
|
+
collection.<<(*args);
|
139
|
+
end", __FILE__, __LINE__)
|
140
|
+
else
|
141
|
+
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
142
|
+
collection = self.send(:#{name})
|
143
|
+
if args.size > 0 and args.all?{|x| x.kind_of?(#{collector_class})}
|
144
|
+
collection.<<(*args)
|
145
|
+
else
|
146
|
+
# FIXME: this is a wierd one - need name here - can't use collection directly...?
|
147
|
+
#{name} << #{collector_class}.new(*args, &block)
|
148
|
+
# this is OK
|
149
|
+
#self.send(:#{name}) << #{collector_class}.new(*args, &block)
|
150
|
+
# but this isn't
|
151
|
+
#collection.<<(#{collector_class}.new(*args, &block))
|
152
|
+
end
|
153
|
+
end", __FILE__, __LINE__)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class KeyedAttribute
|
160
|
+
|
161
|
+
# define a collector for keyed collections
|
162
|
+
# - collection should provide :[], :clone and :replace methods
|
163
|
+
def define_collector
|
164
|
+
collector_spec.each do |collector_name, collector_class|
|
165
|
+
# need to use string eval because passing block
|
166
|
+
# FIXME: don't use eval in 1.9+
|
167
|
+
if collector_class.nil?
|
168
|
+
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
169
|
+
collection = #{name}
|
170
|
+
args.each do |arg|
|
171
|
+
#{name}[arg.send(:#{key})] = arg
|
172
|
+
end
|
173
|
+
end", __FILE__, __LINE__)
|
174
|
+
else
|
175
|
+
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
176
|
+
collection = #{name}
|
177
|
+
if args.size > 0 and args.all?{|x| x.kind_of?(#{collector_class})}
|
178
|
+
args.each do |arg|
|
179
|
+
#{name}[arg.send(:#{key})] = arg
|
180
|
+
end
|
181
|
+
else
|
182
|
+
obj = #{collector_class}.new(*args, &block)
|
183
|
+
#{name}[obj.send(:#{key})] = obj
|
184
|
+
end
|
185
|
+
end", __FILE__, __LINE__)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|