doodle 0.2.2 → 0.2.3
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.
- 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
|