doodle 0.0.10 → 0.1.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.
- data/CREDITS +22 -0
- data/{ChangeLog → History.txt} +22 -3
- data/License.txt +20 -0
- data/Manifest.txt +61 -0
- data/README.txt +166 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +77 -0
- data/config/requirements.rb +15 -0
- data/examples/doodle-errors.rb +25 -0
- data/examples/event-location.rb +3 -7
- data/examples/example-01.rb +1 -1
- data/examples/example-01.rdoc +3 -3
- data/examples/example-02.rb +15 -4
- data/examples/example-02.rdoc +17 -5
- data/examples/mail-datatypes.rb +104 -0
- data/examples/mail.rb +85 -0
- data/examples/parent.rb +40 -0
- data/examples/profile-options.rb +67 -0
- data/examples/smtp_tls.rb +65 -0
- data/examples/test-datatypes.rb +55 -0
- data/examples/yaml-example.rb +40 -0
- data/examples/yaml-example2.rb +42 -0
- data/lib/doodle.rb +364 -301
- data/lib/doodle/datatypes.rb +148 -0
- data/lib/doodle/rfc822.rb +31 -0
- data/lib/doodle/utils.rb +13 -0
- data/lib/doodle/version.rb +9 -0
- data/log/debug.log +0 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +82 -0
- data/setup.rb +1585 -0
- data/spec/arg_order_spec.rb +5 -5
- data/spec/attributes_spec.rb +66 -24
- data/spec/bugs_spec.rb +109 -6
- data/spec/class_spec.rb +7 -4
- data/spec/class_validation_spec.rb +46 -0
- data/spec/class_var_spec.rb +76 -0
- data/spec/collector_spec.rb +16 -30
- data/spec/conversion_spec.rb +8 -3
- data/spec/defaults_spec.rb +4 -4
- data/spec/doodle_context_spec.rb +3 -4
- data/spec/doodle_spec.rb +25 -15
- data/spec/extra_args_spec.rb +1 -1
- data/spec/factory_spec.rb +3 -3
- data/spec/init_spec.rb +11 -11
- data/spec/new_doodle_spec.rb +19 -0
- data/spec/required_spec.rb +1 -1
- data/spec/serialization_spec.rb +3 -6
- data/spec/singleton_spec.rb +5 -5
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/superclass_spec.rb +6 -5
- data/spec/validation2_spec.rb +248 -0
- data/spec/validation_spec.rb +26 -37
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- metadata +76 -20
- data/COPYING +0 -18
- data/README +0 -57
- data/examples/event.rb +0 -39
- data/examples/example-03.rb +0 -45
- data/examples/example-03.rdoc +0 -55
- data/lib/semantic.cache +0 -8
@@ -0,0 +1,55 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), '.'))
|
3
|
+
|
4
|
+
require 'doodle/datatypes'
|
5
|
+
require 'doodle/utils'
|
6
|
+
|
7
|
+
class DateRange < Doodle
|
8
|
+
doodle do
|
9
|
+
date :start
|
10
|
+
date :end do
|
11
|
+
default { start + 1 }
|
12
|
+
end
|
13
|
+
version :version, :default => "0.0.1"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#pp DateRange.instance_methods(false)
|
18
|
+
|
19
|
+
module UserTypes
|
20
|
+
# include Doodle::DataTypes
|
21
|
+
def printable(name, params = { }, &block)
|
22
|
+
string(name, params, &block).instance_eval do
|
23
|
+
must "not contain non-printing characters" do |s|
|
24
|
+
s !~ /[\x00-\x1F]/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def name(name, params = { }, &block)
|
29
|
+
printable(name, { :size => 1..255 }.merge(params), &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Person < Doodle
|
34
|
+
doodle UserTypes do
|
35
|
+
# string :name, :max => 10
|
36
|
+
name :name, :size => 3..10
|
37
|
+
integer :age
|
38
|
+
email :email, :default => ''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
pp try { DateRange "2007-01-18", :version => [0,0,9] }
|
43
|
+
pp try { Person 'Sean', '45', 'someone@example.com' }
|
44
|
+
pp try { Person 'Sean', '45' }
|
45
|
+
pp try { Person 'Sean', 'old' }
|
46
|
+
pp try { Person 'Sean', 45, 'this is not an email address' }
|
47
|
+
pp try { Person 'This name is too long', 45 }
|
48
|
+
pp try { Person 'Sean', 45, 42 }
|
49
|
+
pp try { Person 'A', 45 }
|
50
|
+
pp try { Person '123', 45 }
|
51
|
+
pp try { Person '', 45 }
|
52
|
+
# pp try {
|
53
|
+
# person = Person 'Sean', 45
|
54
|
+
# person.name.silly
|
55
|
+
# }
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'doodle'
|
3
|
+
require 'doodle/utils'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
class Foo < Doodle
|
7
|
+
has :name, :kind => String
|
8
|
+
end
|
9
|
+
|
10
|
+
# load valid data
|
11
|
+
str = %[
|
12
|
+
--- !ruby/object:Foo
|
13
|
+
name: Stimpy
|
14
|
+
]
|
15
|
+
bar = nil
|
16
|
+
rv = try do
|
17
|
+
bar = YAML::load(str).validate!
|
18
|
+
end
|
19
|
+
rv # => #<Foo:0xb7b02cc0 @name="Stimpy">
|
20
|
+
bar # => #<Foo:0xb7b02cc0 @name="Stimpy">
|
21
|
+
|
22
|
+
str = %[
|
23
|
+
--- !ruby/object:Foo
|
24
|
+
name: 1
|
25
|
+
]
|
26
|
+
|
27
|
+
# load invalid data
|
28
|
+
baz = nil
|
29
|
+
rv = try do
|
30
|
+
baz = YAML::load(str).validate!
|
31
|
+
end
|
32
|
+
rv # => #<Doodle::ValidationError: name must be String - got Fixnum(1)>
|
33
|
+
baz # => nil
|
34
|
+
|
35
|
+
# load from hash
|
36
|
+
str = %[
|
37
|
+
name: Qux
|
38
|
+
]
|
39
|
+
|
40
|
+
qux = Foo(YAML::load(str)) # => #<Foo:0xb7aff520 @name="Qux">
|
@@ -0,0 +1,42 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), '.'))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'yaml'
|
6
|
+
require 'doodle'
|
7
|
+
require 'pp'
|
8
|
+
|
9
|
+
class AddressLine < Doodle
|
10
|
+
has :text, :kind => String
|
11
|
+
end
|
12
|
+
|
13
|
+
class Person < Doodle
|
14
|
+
has :name, :kind => String
|
15
|
+
has :address, :collect => { :line => AddressLine }
|
16
|
+
end
|
17
|
+
|
18
|
+
yaml = %[
|
19
|
+
---
|
20
|
+
:address:
|
21
|
+
- Henry Wood House
|
22
|
+
- London
|
23
|
+
:name: Sean
|
24
|
+
]
|
25
|
+
|
26
|
+
person = Person(YAML.load(yaml))
|
27
|
+
pp person
|
28
|
+
yaml = person.to_yaml
|
29
|
+
puts yaml
|
30
|
+
|
31
|
+
yaml = %[
|
32
|
+
--- !ruby/object:Person
|
33
|
+
address:
|
34
|
+
- !ruby/object:AddressLine
|
35
|
+
text: Henry Wood House
|
36
|
+
- !ruby/object:AddressLine
|
37
|
+
text: London
|
38
|
+
name: Sean
|
39
|
+
]
|
40
|
+
person = YAML.load(yaml).validate!
|
41
|
+
pp person
|
42
|
+
|
data/lib/doodle.rb
CHANGED
@@ -1,46 +1,37 @@
|
|
1
1
|
# doodle
|
2
|
-
# Copyright (C) 2007 by Sean O'Halpin
|
2
|
+
# Copyright (C) 2007-2008 by Sean O'Halpin
|
3
|
+
# 2007-11-24 first version
|
4
|
+
# 2008-04-18 latest release 0.0.12
|
5
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
6
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
7
|
|
4
8
|
require 'molic_orderedhash' # todo[replace this with own (required functions only) version]
|
5
|
-
require 'pp'
|
6
9
|
#require 'bleak_house' if ENV['BLEAK_HOUSE']
|
7
10
|
|
11
|
+
# require Ruby 1.8.6 or higher
|
12
|
+
if RUBY_VERSION < '1.8.6'
|
13
|
+
raise Exception, "Sorry - doodle does not work with versions of Ruby below 1.8.6"
|
14
|
+
end
|
15
|
+
|
8
16
|
# *doodle* is my attempt at an eco-friendly metaprogramming framework that does not
|
9
17
|
# have pollute core Ruby objects such as Object, Class and Module.
|
10
|
-
|
18
|
+
#
|
11
19
|
# While doodle itself is useful for defining classes, my main goal is to
|
12
20
|
# come up with a useful DSL notation for class definitions which can be
|
13
21
|
# reused in many contexts.
|
14
|
-
|
22
|
+
#
|
15
23
|
# Docs at http://doodle.rubyforge.org
|
16
|
-
|
17
|
-
|
18
|
-
# note: this does not seem to work so well with singletons
|
19
|
-
if RUBY_VERSION < '1.8.6'
|
20
|
-
if !Object.method_defined?(:instance_variable_defined?)
|
21
|
-
class Object
|
22
|
-
__doodle__inspect = instance_method(:inspect)
|
23
|
-
define_method :instance_variable_defined? do |name|
|
24
|
-
res = __doodle__inspect.bind(self).call
|
25
|
-
rx = /\B#{name}=/
|
26
|
-
rx =~ res ? true : false
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
module Doodle
|
33
|
-
VERSION = '0.0.10'
|
34
|
-
# where are we?
|
24
|
+
#
|
25
|
+
class Doodle
|
35
26
|
class << self
|
27
|
+
# provide somewhere to hold thread-specific context information
|
28
|
+
# (I'm claiming the :doodle_xxx namespace)
|
36
29
|
def context
|
37
30
|
Thread.current[:doodle_context] ||= []
|
38
31
|
end
|
39
|
-
def parent
|
40
|
-
context[-2]
|
41
|
-
end
|
42
32
|
end
|
43
33
|
|
34
|
+
# debugging utilities
|
44
35
|
module Debug
|
45
36
|
class << self
|
46
37
|
# output result of block if ENV['DEBUG_DOODLE'] set
|
@@ -50,15 +41,40 @@ module Doodle
|
|
50
41
|
end
|
51
42
|
end
|
52
43
|
|
53
|
-
#
|
54
|
-
module
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
44
|
+
# Place to hold ref to built-in classes that need special handling
|
45
|
+
module BuiltIns
|
46
|
+
BUILTINS = [String, Hash, Array]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set of utility functions to avoid monkeypatching base classes
|
50
|
+
module Utils
|
51
|
+
class << self
|
52
|
+
# Unnest arrays by one level of nesting, e.g. [1, [[2], 3]] => [1, [2], 3].
|
53
|
+
def flatten_first_level(enum)
|
54
|
+
enum.inject([]) {|arr, i| if i.kind_of? Array then arr.push(*i) else arr.push(i) end }
|
55
|
+
end
|
56
|
+
# from facets/string/case.rb, line 80
|
57
|
+
def snake_case(camel_cased_word)
|
58
|
+
camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
59
|
+
end
|
60
|
+
# what kind of object are we dealing with?
|
61
|
+
def doodle_category(obj)
|
62
|
+
# note[this uses regex match on object's inspect string - kludgy
|
63
|
+
# - is there a better way?]
|
64
|
+
return :nil if obj.class == NilClass
|
65
|
+
case obj.real_inspect
|
66
|
+
when /#<Class:#<.*0x[a-z0-9]+>+$/
|
67
|
+
:instance_singleton_class
|
68
|
+
when /#<Class:[A-Z]/
|
69
|
+
:class_singleton_class
|
70
|
+
else
|
71
|
+
if obj.kind_of?(Module)
|
72
|
+
:class
|
73
|
+
else
|
74
|
+
:instance
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
62
78
|
end
|
63
79
|
end
|
64
80
|
|
@@ -70,14 +86,14 @@ module Doodle
|
|
70
86
|
def self.raise_exception_on_error=(tf)
|
71
87
|
@@raise_exception_on_error = tf
|
72
88
|
end
|
73
|
-
|
89
|
+
|
74
90
|
# internal error raised when a default was expected but not found
|
75
91
|
class NoDefaultError < Exception
|
76
92
|
end
|
77
93
|
# raised when a validation rule returns false
|
78
94
|
class ValidationError < Exception
|
79
95
|
end
|
80
|
-
# raised when
|
96
|
+
# raised when an unknown parameter is passed to initialize
|
81
97
|
class UnknownAttributeError < Exception
|
82
98
|
end
|
83
99
|
# raised when a conversion fails
|
@@ -90,7 +106,6 @@ module Doodle
|
|
90
106
|
# provides more direct access to the singleton class and a way to
|
91
107
|
# treat Modules and Classes equally in a meta context
|
92
108
|
module SelfClass
|
93
|
-
|
94
109
|
# return the 'singleton class' of an object, optionally executing
|
95
110
|
# a block argument in the (module/class) context of that object
|
96
111
|
def singleton_class(&block)
|
@@ -98,15 +113,14 @@ module Doodle
|
|
98
113
|
sc.module_eval(&block) if block_given?
|
99
114
|
sc
|
100
115
|
end
|
101
|
-
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
!is_class_self_defn? && !is_singleton_defn?
|
116
|
+
# evaluate in class context of self, whether Class, Module or singleton
|
117
|
+
def sc_eval(*args, &block)
|
118
|
+
if self.kind_of?(Module)
|
119
|
+
klass = self
|
120
|
+
else
|
121
|
+
klass = self.singleton_class
|
122
|
+
end
|
123
|
+
klass.module_eval(*args, &block)
|
110
124
|
end
|
111
125
|
end
|
112
126
|
|
@@ -114,67 +128,40 @@ module Doodle
|
|
114
128
|
# classes as well as modules, classes and instances
|
115
129
|
module Inherited
|
116
130
|
|
117
|
-
# parents returns the set of parent classes of an object
|
118
|
-
# note[this is horribly complicated and kludgy - is there a better way?
|
119
|
-
# could do with refactoring]
|
120
|
-
|
121
|
-
# this function is a ~mess~ - refactor!!!
|
131
|
+
# parents returns the set of parent classes of an object
|
122
132
|
def parents
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if self != superclass
|
128
|
-
#
|
129
|
-
# fixme[any other way to do this? seems really clunky to have to hack strings]
|
130
|
-
#
|
131
|
-
# What's this doing? Finding the class of which this is the singleton class
|
132
|
-
regexen = [/Class:(?:#<)?([A-Z_][A-Za-z_]+)/, /Class:(([A-Z_][A-Za-z_]+))/]
|
133
|
-
regexen.each do |regex|
|
134
|
-
if cap = self.to_s.match(regex)
|
135
|
-
if cap.captures.size > 0
|
136
|
-
k = const_get(cap[1])
|
137
|
-
# push onto front of array
|
138
|
-
if k.respond_to?(:superclass) && k.superclass.respond_to?(:singleton_class)
|
139
|
-
klasses.unshift k.superclass.singleton_class
|
140
|
-
end
|
141
|
-
end
|
142
|
-
until klass.nil?
|
143
|
-
klasses.unshift klass
|
144
|
-
if klass == klass.superclass
|
145
|
-
return klasses # oof
|
146
|
-
end
|
147
|
-
klass = klass.superclass
|
148
|
-
end
|
149
|
-
else
|
150
|
-
until klass.nil?
|
151
|
-
klasses << klass
|
152
|
-
klass = klass.superclass
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
else
|
133
|
+
# if singleton class (e.g. class << [Ff]oo; self; end) then it has
|
134
|
+
# no parents
|
135
|
+
case Doodle::Utils.doodle_category(self)
|
136
|
+
when :instance
|
158
137
|
klass = self.class
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
138
|
+
when :instance_singleton_class
|
139
|
+
klass = nil
|
140
|
+
when :class
|
141
|
+
klass = superclass
|
142
|
+
when :class_singleton_class
|
143
|
+
klass = nil
|
144
|
+
end
|
145
|
+
klasses = []
|
146
|
+
until klass.nil? || klass == klass.superclass
|
147
|
+
klasses << klass
|
148
|
+
klass = klass.superclass
|
163
149
|
end
|
164
150
|
klasses
|
165
151
|
end
|
166
|
-
|
152
|
+
|
153
|
+
# need concepts of
|
154
|
+
# - attributes
|
155
|
+
# - instance_attributes
|
156
|
+
# - singleton_attributes
|
157
|
+
# - class_attributes
|
158
|
+
|
167
159
|
# send message to all parents and collect results
|
168
160
|
def collect_inherited(message)
|
169
161
|
result = []
|
170
|
-
|
171
|
-
#p [:parents, parents]
|
172
|
-
# d { [:collect_inherited, :parents, message, klasses] }
|
173
|
-
klasses.each do |klass|
|
174
|
-
#p [:testing, klass]
|
162
|
+
parents.each do |klass|
|
175
163
|
if klass.respond_to?(message)
|
176
|
-
|
177
|
-
result.unshift(*klass.send(message))
|
164
|
+
result.unshift(*klass.__send__(message))
|
178
165
|
else
|
179
166
|
break
|
180
167
|
end
|
@@ -184,9 +171,11 @@ module Doodle
|
|
184
171
|
private :collect_inherited
|
185
172
|
end
|
186
173
|
|
187
|
-
# the intent of embrace is to provide a way to create directives
|
188
|
-
# affect all members of a class 'family' without having to
|
189
|
-
# Module, Class or Object - in some ways, it's similar to Ara
|
174
|
+
# the intent of embrace is to provide a way to create directives
|
175
|
+
# that affect all members of a class 'family' without having to
|
176
|
+
# modify Module, Class or Object - in some ways, it's similar to Ara
|
177
|
+
# Howard's mixable[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/197296]
|
178
|
+
# though not as tidy :S
|
190
179
|
#
|
191
180
|
# this works down to third level <tt>class << self</tt> - in practice, this is
|
192
181
|
# perfectly good - it would be great to have a completely general
|
@@ -197,9 +186,8 @@ module Doodle
|
|
197
186
|
def embrace(other, &block)
|
198
187
|
# include in instance method chain
|
199
188
|
include other
|
200
|
-
#extend other
|
201
189
|
sc = class << self; self; end
|
202
|
-
sc.
|
190
|
+
sc.module_eval {
|
203
191
|
# class method chain
|
204
192
|
include other
|
205
193
|
# singleton method chain
|
@@ -207,16 +195,17 @@ module Doodle
|
|
207
195
|
# ensure that subclasses are also embraced
|
208
196
|
define_method :inherited do |klass|
|
209
197
|
#p [:embrace, :inherited, klass]
|
210
|
-
klass.
|
211
|
-
klass.
|
198
|
+
klass.__send__(:embrace, other) # n.b. closure
|
199
|
+
klass.__send__(:include, Factory) # is there another way to do this? i.e. not in embrace
|
212
200
|
super(klass) if defined?(super)
|
213
201
|
end
|
214
202
|
}
|
215
|
-
sc.
|
203
|
+
sc.module_eval(&block) if block_given?
|
216
204
|
end
|
217
205
|
end
|
218
206
|
|
219
|
-
|
207
|
+
# save a block for later execution
|
208
|
+
class DeferredBlock
|
220
209
|
attr_accessor :block
|
221
210
|
def initialize(arg_block = nil, &block)
|
222
211
|
arg_block = block if block_given?
|
@@ -238,6 +227,7 @@ module Doodle
|
|
238
227
|
end
|
239
228
|
end
|
240
229
|
|
230
|
+
# place to stash bookkeeping info
|
241
231
|
class DoodleInfo
|
242
232
|
attr_accessor :local_attributes
|
243
233
|
attr_accessor :local_validations
|
@@ -245,6 +235,7 @@ module Doodle
|
|
245
235
|
attr_accessor :validation_on
|
246
236
|
attr_accessor :arg_order
|
247
237
|
attr_accessor :errors
|
238
|
+
attr_accessor :parent
|
248
239
|
|
249
240
|
def initialize(object)
|
250
241
|
@local_attributes = OrderedHash.new
|
@@ -253,14 +244,70 @@ module Doodle
|
|
253
244
|
@local_conversions = {}
|
254
245
|
@arg_order = []
|
255
246
|
@errors = []
|
247
|
+
@parent = nil
|
248
|
+
end
|
249
|
+
real_inspect = Object.instance_method(:inspect)
|
250
|
+
define_method :real_inspect do
|
251
|
+
real_inspect.bind(self).call
|
252
|
+
end
|
253
|
+
def inspect
|
254
|
+
''
|
256
255
|
end
|
257
256
|
end
|
258
257
|
|
258
|
+
# what it says on the tin :) various hacks to hide @__doodle__ variable
|
259
|
+
module SmokeAndMirrors
|
260
|
+
# redefine instance_variables to ignore our private @__doodle__ variable
|
261
|
+
# (hack to fool yaml and anything else that queries instance_variables)
|
262
|
+
meth = Object.instance_method(:instance_variables)
|
263
|
+
define_method :instance_variables do
|
264
|
+
meth.bind(self).call.reject{ |x| x =~ /@__doodle__/}
|
265
|
+
end
|
266
|
+
|
267
|
+
# hide @__doodle__ from inspect
|
268
|
+
# variants:
|
269
|
+
# #<Foo:0xb7de0064>
|
270
|
+
# #<Foo:0xb7c52d28 @name="Arthur Dent", @age=42>
|
271
|
+
# #<Class:#<Foo:0xb7de0064>>
|
272
|
+
# #<Class:Class>
|
273
|
+
# #<Class:#<Class:#<Foo:0xb7de0064>>>
|
274
|
+
|
275
|
+
# strip off all trailing > (count them = nesting)
|
276
|
+
# take leading chars up to first space (or EOS)
|
277
|
+
# rebuild rest
|
278
|
+
|
279
|
+
real_inspect = Object.instance_method(:inspect)
|
280
|
+
define_method :real_inspect do
|
281
|
+
real_inspect.bind(self).call
|
282
|
+
end
|
283
|
+
define_method :inspect do
|
284
|
+
# have to handle some built-ins as special case
|
285
|
+
built_in = Doodle::BuiltIns::BUILTINS.select{ |x| self.kind_of?(x) }.first
|
286
|
+
if built_in
|
287
|
+
built_in.instance_method(:inspect).bind(self).call
|
288
|
+
else
|
289
|
+
istr = real_inspect.bind(self).call
|
290
|
+
str = istr.gsub(/(>+$)/, '')
|
291
|
+
trailing = $1
|
292
|
+
klass = str.split(/\s/, 2).first
|
293
|
+
ivars = self.kind_of?(Module) ? [] : instance_variables
|
294
|
+
separator = ivars.size > 0 ? ' ' : ''
|
295
|
+
|
296
|
+
#pp [:istr, istr, :str, str, :trailing, trailing, :klass, klass]
|
297
|
+
# note to self: changing klass to <self.class will highlight cases that need the hack in parents
|
298
|
+
#%[<#{self.class}#{separator}#{instance_variables.map{|x| "#{x}=#{instance_variable_get(x).inspect}"}.join(' ')}#{trailing}]
|
299
|
+
%[#{klass}#{separator}#{ivars.map{|x| "#{x}=#{instance_variable_get(x).inspect}"}.join(' ')}#{trailing}]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
259
305
|
# the core module of Doodle - to get most facilities provided by Doodle
|
260
|
-
# without inheriting from Doodle
|
306
|
+
# without inheriting from Doodle, include Doodle::Core, not this module
|
261
307
|
module BaseMethods
|
262
308
|
include SelfClass
|
263
309
|
include Inherited
|
310
|
+
include SmokeAndMirrors
|
264
311
|
|
265
312
|
# this is the only way to get at internal values. Note: this is
|
266
313
|
# initialized on the fly rather than in #initialize because
|
@@ -270,17 +317,12 @@ module Doodle
|
|
270
317
|
end
|
271
318
|
private :__doodle__
|
272
319
|
|
273
|
-
#
|
274
|
-
# pick a name that no-one else is likely to use
|
275
|
-
alias :seoh_doodle_instance_variables_8b016735_bd60_44d9_bda5_5f9d0aade5a6 :instance_variables
|
276
|
-
# redefine instance_variables to ignore our private @__doodle__ variable
|
277
|
-
def instance_variables
|
278
|
-
seoh_doodle_instance_variables_8b016735_bd60_44d9_bda5_5f9d0aade5a6.reject{ |x| x == '@__doodle__'}
|
279
|
-
end
|
280
|
-
|
320
|
+
# helper for Marshal.dump
|
281
321
|
def marshal_dump
|
322
|
+
# note: perhaps should also dump singleton attribute definitions?
|
282
323
|
instance_variables.map{|x| [x, instance_variable_get(x)] }
|
283
324
|
end
|
325
|
+
# helper for Marshal.load
|
284
326
|
def marshal_load(data)
|
285
327
|
data.each do |name, value|
|
286
328
|
instance_variable_set(name, value)
|
@@ -291,7 +333,8 @@ module Doodle
|
|
291
333
|
def errors
|
292
334
|
__doodle__.errors
|
293
335
|
end
|
294
|
-
|
336
|
+
|
337
|
+
# clear out the errors collection
|
295
338
|
def clear_errors
|
296
339
|
#pp [:clear_errors, self, caller]
|
297
340
|
__doodle__.errors.clear
|
@@ -299,8 +342,6 @@ module Doodle
|
|
299
342
|
|
300
343
|
# handle errors either by collecting in :errors or raising an exception
|
301
344
|
def handle_error(name, *args)
|
302
|
-
#pp [:caller, self, caller]
|
303
|
-
#pp [:handle_error, name, args]
|
304
345
|
# don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
|
305
346
|
if !self.errors.include?([name, *args])
|
306
347
|
self.errors << [name, *args]
|
@@ -310,6 +351,17 @@ module Doodle
|
|
310
351
|
end
|
311
352
|
end
|
312
353
|
|
354
|
+
def _handle_inherited_hash(tf, method)
|
355
|
+
if tf
|
356
|
+
collect_inherited(method).inject(OrderedHash.new){ |hash, item|
|
357
|
+
hash.merge(OrderedHash[*item])
|
358
|
+
}.merge(__send__(method))
|
359
|
+
else
|
360
|
+
__send__(method)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
private :_handle_inherited_hash
|
364
|
+
|
313
365
|
# return attributes defined in instance
|
314
366
|
def local_attributes
|
315
367
|
__doodle__.local_attributes
|
@@ -320,18 +372,39 @@ module Doodle
|
|
320
372
|
# - if tf == true, returns all inherited attributes
|
321
373
|
# - if tf == false, returns only those attributes defined in the current object/class
|
322
374
|
def attributes(tf = true)
|
323
|
-
|
324
|
-
|
325
|
-
|
375
|
+
results = _handle_inherited_hash(tf, :local_attributes)
|
376
|
+
if !kind_of?(Class) && singleton_class.respond_to?(:attributes)
|
377
|
+
results = results.merge(singleton_class.attributes)
|
378
|
+
end
|
379
|
+
results
|
380
|
+
end
|
381
|
+
|
382
|
+
# return attributes for class
|
383
|
+
def class_attributes(tf = true)
|
384
|
+
attrs = OrderedHash.new
|
385
|
+
if self.kind_of?(Class)
|
386
|
+
attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
|
326
387
|
hash.merge(OrderedHash[*item])
|
327
|
-
}.merge(
|
328
|
-
|
329
|
-
a
|
388
|
+
}.merge(singleton_class.respond_to?(:attributes) ? singleton_class.attributes : { })
|
389
|
+
attrs
|
330
390
|
else
|
331
|
-
|
391
|
+
self.class.class_attributes
|
332
392
|
end
|
333
393
|
end
|
334
394
|
|
395
|
+
# the set of conversions defined in the current class (i.e. without inheritance)
|
396
|
+
def local_conversions
|
397
|
+
__doodle__.local_conversions
|
398
|
+
end
|
399
|
+
protected :local_conversions
|
400
|
+
|
401
|
+
# returns hash of conversions
|
402
|
+
# - if tf == true, returns all inherited conversions
|
403
|
+
# - if tf == false, returns only those conversions defined in the current object/class
|
404
|
+
def conversions(tf = true)
|
405
|
+
_handle_inherited_hash(tf, :local_conversions)
|
406
|
+
end
|
407
|
+
|
335
408
|
# the set of validations defined in the current class (i.e. without inheritance)
|
336
409
|
def local_validations
|
337
410
|
__doodle__.local_validations
|
@@ -343,51 +416,35 @@ module Doodle
|
|
343
416
|
# - if tf == false, returns only those validations defined in the current object/class
|
344
417
|
def validations(tf = true)
|
345
418
|
if tf
|
346
|
-
#
|
347
|
-
#
|
419
|
+
# note: validations are handled differently to attributes and
|
420
|
+
# conversions because ~all~ validations apply (so are stored
|
421
|
+
# as an array), whereas attributes and conversions are keyed
|
422
|
+
# by name and kind respectively, so only the most recent
|
423
|
+
# applies
|
424
|
+
|
348
425
|
local_validations + collect_inherited(:local_validations)
|
349
426
|
else
|
350
427
|
local_validations
|
351
428
|
end
|
352
429
|
end
|
353
430
|
|
354
|
-
# the set of conversions defined in the current class (i.e. without inheritance)
|
355
|
-
def local_conversions
|
356
|
-
__doodle__.local_conversions
|
357
|
-
end
|
358
|
-
protected :local_conversions
|
359
|
-
|
360
|
-
# returns array of conversions
|
361
|
-
# - if tf == true, returns all inherited conversions
|
362
|
-
# - if tf == false, returns only those conversions defined in the current object/class
|
363
|
-
def conversions(tf = true)
|
364
|
-
if tf
|
365
|
-
a = collect_inherited(:local_conversions).inject(OrderedHash.new){ |hash, item|
|
366
|
-
#p [:hash, hash, :item, item]
|
367
|
-
hash.merge(OrderedHash[*item])
|
368
|
-
}.merge(self.local_conversions)
|
369
|
-
# d { [:conversions, self.to_s, a] }
|
370
|
-
a
|
371
|
-
else
|
372
|
-
local_conversions
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
431
|
# lookup a single attribute by name, searching the singleton class first
|
377
432
|
def lookup_attribute(name)
|
378
433
|
# (look at singleton attributes first)
|
379
|
-
# fixme[this smells like a hack to me
|
380
|
-
|
434
|
+
# fixme[this smells like a hack to me]
|
435
|
+
if self.class == Class
|
436
|
+
class_attributes[name]
|
437
|
+
else
|
438
|
+
attributes[name]
|
439
|
+
end
|
381
440
|
end
|
382
441
|
private :lookup_attribute
|
383
442
|
|
384
443
|
# either get an attribute value (if no args given) or set it
|
385
444
|
# (using args and/or block)
|
386
445
|
def getter_setter(name, *args, &block)
|
387
|
-
# d { [:getter_setter, name, args, block] }
|
388
446
|
name = name.to_sym
|
389
447
|
if block_given? || args.size > 0
|
390
|
-
# setter
|
391
448
|
_setter(name, *args, &block)
|
392
449
|
else
|
393
450
|
_getter(name)
|
@@ -397,21 +454,20 @@ module Doodle
|
|
397
454
|
|
398
455
|
# get an attribute by name - return default if not otherwise defined
|
399
456
|
def _getter(name, &block)
|
400
|
-
## d { [:_getter, 1, self.to_s, name, block, instance_variables] }
|
401
|
-
# getter
|
402
457
|
ivar = "@#{name}"
|
403
458
|
if instance_variable_defined?(ivar)
|
404
|
-
## d { [:_getter, 2, name, block] }
|
405
459
|
instance_variable_get(ivar)
|
406
460
|
else
|
407
461
|
# handle default
|
408
462
|
# Note: use :init => value to cover cases where defaults don't work
|
409
463
|
# (e.g. arrays that disappear when you go out of scope)
|
410
464
|
att = lookup_attribute(name)
|
411
|
-
#
|
412
|
-
if att.
|
465
|
+
# special case for class/singleton :init
|
466
|
+
if att.init_defined?
|
467
|
+
_setter(name, att.init)
|
468
|
+
elsif att.default_defined?
|
413
469
|
case att.default
|
414
|
-
when
|
470
|
+
when DeferredBlock
|
415
471
|
instance_eval(&att.default.block)
|
416
472
|
when Proc
|
417
473
|
instance_eval(&att.default)
|
@@ -428,20 +484,16 @@ module Doodle
|
|
428
484
|
|
429
485
|
# set an attribute by name - apply validation if defined
|
430
486
|
def _setter(name, *args, &block)
|
431
|
-
|
487
|
+
Doodle::Debug.d { [:_setter, name, args] }
|
432
488
|
ivar = "@#{name}"
|
433
489
|
if block_given?
|
434
|
-
args.unshift(
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
att = lookup_attribute(name)
|
439
|
-
# d { [:_setter, 4, :setting, name, att] }
|
440
|
-
if att
|
441
|
-
#d { [:_setter, :instance_variable_set, :ivar, ivar, :args, args, :att_validate, att.validate(*args) ] }
|
490
|
+
args.unshift(DeferredBlock.new(block))
|
491
|
+
end
|
492
|
+
if att = lookup_attribute(name)
|
493
|
+
Doodle::Debug.d { [:_setter, name, args] }
|
442
494
|
v = instance_variable_set(ivar, att.validate(self, *args))
|
443
495
|
else
|
444
|
-
|
496
|
+
Doodle::Debug.d { [:_setter, "no attribute"] }
|
445
497
|
v = instance_variable_set(ivar, *args)
|
446
498
|
end
|
447
499
|
validate!(false)
|
@@ -452,13 +504,11 @@ module Doodle
|
|
452
504
|
# if block passed, define a conversion from class
|
453
505
|
# if no args, apply conversion to arguments
|
454
506
|
def from(*args, &block)
|
455
|
-
# d { [:from, self, self.class, self.name, args, block] }
|
456
507
|
if block_given?
|
457
508
|
# set the rule for each arg given
|
458
509
|
args.each do |arg|
|
459
510
|
local_conversions[arg] = block
|
460
511
|
end
|
461
|
-
# d { [:from, conversions] }
|
462
512
|
else
|
463
513
|
convert(self, *args)
|
464
514
|
end
|
@@ -471,7 +521,6 @@ module Doodle
|
|
471
521
|
|
472
522
|
# add a validation that attribute must be of class <= kind
|
473
523
|
def kind(*args, &block)
|
474
|
-
# d { [:kind, args, block] }
|
475
524
|
if args.size > 0
|
476
525
|
# todo[figure out how to handle kind being specified twice?]
|
477
526
|
@kind = args.first
|
@@ -492,16 +541,14 @@ module Doodle
|
|
492
541
|
ancestors = value.class.ancestors
|
493
542
|
matches = ancestors & conversions.keys
|
494
543
|
indexed_matches = matches.map{ |x| ancestors.index(x)}
|
495
|
-
#p [matches, indexed_matches, indexed_matches.min]
|
496
544
|
if indexed_matches.size > 0
|
497
545
|
converter_class = ancestors[indexed_matches.min]
|
498
|
-
#p [:converter, converter_class]
|
499
546
|
if converter = conversions[converter_class]
|
500
547
|
value = converter[*args]
|
501
548
|
end
|
502
549
|
end
|
503
550
|
end
|
504
|
-
rescue => e
|
551
|
+
rescue Exception => e
|
505
552
|
owner.handle_error name, ConversionError, e.to_s, [caller[-1]]
|
506
553
|
end
|
507
554
|
value
|
@@ -511,34 +558,30 @@ module Doodle
|
|
511
558
|
def validate(owner, *args)
|
512
559
|
Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
|
513
560
|
value = convert(owner, *args)
|
514
|
-
#d { [:validate, self, :args, args, :value, value ] }
|
515
561
|
validations.each do |v|
|
516
562
|
Doodle::Debug.d { [:validate, self, v, args, value] }
|
517
563
|
if !v.block[value]
|
518
564
|
owner.handle_error name, ValidationError, "#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
|
519
565
|
end
|
520
566
|
end
|
521
|
-
#d { [:validate, :value, value ] }
|
522
567
|
value
|
523
568
|
end
|
524
|
-
|
569
|
+
|
525
570
|
# define a getter_setter
|
526
571
|
def define_getter_setter(name, *args, &block)
|
527
|
-
# d { [:define_getter_setter, [self, self.class], name, args, block] }
|
528
|
-
|
529
572
|
# need to use string eval because passing block
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
# this is how it should be done (in 1.9)
|
534
|
-
# module_eval {
|
535
|
-
# define_method name do |*args, &block|
|
536
|
-
# getter_setter(name.to_sym, *args, &block)
|
537
|
-
# end
|
538
|
-
# define_method "#{name}=" do |*args, &block|
|
539
|
-
# _setter(name.to_sym, *args, &block)
|
540
|
-
# end
|
541
|
-
# }
|
573
|
+
sc_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
|
574
|
+
sc_eval "def #{name}=(*args, &block); _setter(:#{name}, *args); end", __FILE__, __LINE__
|
575
|
+
|
576
|
+
# this is how it should be done (in 1.9)
|
577
|
+
# module_eval {
|
578
|
+
# define_method name do |*args, &block|
|
579
|
+
# getter_setter(name.to_sym, *args, &block)
|
580
|
+
# end
|
581
|
+
# define_method "#{name}=" do |*args, &block|
|
582
|
+
# _setter(name.to_sym, *args, &block)
|
583
|
+
# end
|
584
|
+
# }
|
542
585
|
end
|
543
586
|
private :define_getter_setter
|
544
587
|
|
@@ -547,9 +590,15 @@ module Doodle
|
|
547
590
|
def define_collector(collection, name, klass = nil, &block)
|
548
591
|
# need to use string eval because passing block
|
549
592
|
if klass.nil?
|
550
|
-
|
593
|
+
sc_eval("def #{name}(*args, &block); args.unshift(block) if block_given?; #{collection}.<<(*args); end", __FILE__, __LINE__)
|
551
594
|
else
|
552
|
-
|
595
|
+
sc_eval("def #{name}(*args, &block);
|
596
|
+
if args.all?{|x| x.kind_of?(#{klass})}
|
597
|
+
#{collection}.<<(*args)
|
598
|
+
else
|
599
|
+
#{collection} << #{klass}.new(*args, &block);
|
600
|
+
end
|
601
|
+
end", __FILE__, __LINE__)
|
553
602
|
end
|
554
603
|
end
|
555
604
|
private :define_collector
|
@@ -577,13 +626,11 @@ module Doodle
|
|
577
626
|
# end
|
578
627
|
#
|
579
628
|
def has(*args, &block)
|
580
|
-
#what_am_i?([:has, args])
|
581
629
|
Doodle::Debug.d { [:has, self, self.class, args] }
|
582
630
|
name = args.shift.to_sym
|
583
631
|
# d { [:has2, name, args] }
|
584
632
|
key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
|
585
633
|
handle_error name, ArgumentError, "Too many arguments" if positional_args.size > 0
|
586
|
-
# d { [:has_args, self, key_values, positional_args, args] }
|
587
634
|
params = { :name => name }
|
588
635
|
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
589
636
|
|
@@ -607,7 +654,6 @@ module Doodle
|
|
607
654
|
define_collector name, collector_name, collector_klass
|
608
655
|
end
|
609
656
|
|
610
|
-
# d { [:has_args, :params, params] }
|
611
657
|
# define getter setter before setting up attribute
|
612
658
|
define_getter_setter name, *args, &block
|
613
659
|
local_attributes[name] = attribute = Attribute.new(params, &block)
|
@@ -620,23 +666,18 @@ module Doodle
|
|
620
666
|
else
|
621
667
|
tmp_klass = collector_klass
|
622
668
|
end
|
623
|
-
enum.map{|x|
|
669
|
+
enum.map{|x|
|
670
|
+
if x.kind_of?(tmp_klass)
|
671
|
+
x
|
672
|
+
elsif tmp_klass.conversions.key?(x.class)
|
673
|
+
tmp_klass.from(x)
|
674
|
+
else
|
675
|
+
tmp_klass.new(x)
|
676
|
+
end
|
677
|
+
}
|
624
678
|
end
|
625
679
|
}
|
626
680
|
end
|
627
|
-
if is_class_self_defn? or is_singleton_defn?
|
628
|
-
#pp [:args, args]
|
629
|
-
init_values = get_init_values(false)
|
630
|
-
# if init_values.size > 0
|
631
|
-
#p [:init_values, name, self, is_class_self_defn? ? :CLASS : is_singleton_defn? ? :SINGLETON : '?', init_values, methods(false)]
|
632
|
-
# #define_getter_setter(name)
|
633
|
-
# #instance_variable_set("@#{name}", init_values[name])
|
634
|
-
# end
|
635
|
-
# # singleton_class do
|
636
|
-
# #_setter(name, init_values[name])
|
637
|
-
# # end
|
638
|
-
end
|
639
|
-
|
640
681
|
attribute
|
641
682
|
end
|
642
683
|
|
@@ -644,7 +685,6 @@ module Doodle
|
|
644
685
|
def arg_order(*args)
|
645
686
|
if args.size > 0
|
646
687
|
begin
|
647
|
-
#p [:arg_order, 1, self, self.class, args]
|
648
688
|
args.uniq!
|
649
689
|
args.each do |x|
|
650
690
|
handle_error :arg_order, ArgumentError, "#{x} not a Symbol" if !(x.class <= Symbol)
|
@@ -652,11 +692,9 @@ module Doodle
|
|
652
692
|
end
|
653
693
|
__doodle__.arg_order = args
|
654
694
|
rescue Exception => e
|
655
|
-
#p [InvalidOrderError, e.to_s]
|
656
695
|
handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
|
657
696
|
end
|
658
697
|
else
|
659
|
-
#p [:arg_order, 3, self, self.class, :default]
|
660
698
|
__doodle__.arg_order + (attributes.keys - __doodle__.arg_order)
|
661
699
|
end
|
662
700
|
end
|
@@ -666,63 +704,18 @@ module Doodle
|
|
666
704
|
hash[n] = begin
|
667
705
|
case a.init
|
668
706
|
when NilClass, TrueClass, FalseClass, Fixnum
|
669
|
-
#p [:init, :no_clone]
|
670
707
|
a.init
|
671
|
-
when
|
672
|
-
#p [:init, :save_block]
|
708
|
+
when DeferredBlock
|
673
709
|
instance_eval(&a.init.block)
|
674
710
|
else
|
675
|
-
#p [:init, :clone]
|
676
711
|
a.init.clone
|
677
712
|
end
|
678
713
|
rescue Exception => e
|
679
|
-
#p [:init, :rescue, e]
|
680
714
|
a.init
|
681
715
|
end
|
682
716
|
; hash }
|
683
717
|
end
|
684
718
|
private :get_init_values
|
685
|
-
|
686
|
-
# helper function to initialize from hash - this is safe to use
|
687
|
-
# after initialization (validate! is called if this method is
|
688
|
-
# called after initialization)
|
689
|
-
def initialize_from_hash(*args)
|
690
|
-
defer_validation do
|
691
|
-
# hash initializer
|
692
|
-
# separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
|
693
|
-
key_values, args = args.partition{ |x| x.kind_of?(Hash)}
|
694
|
-
Doodle::Debug.d { [:initialize_from_hash, :key_values, key_values, :args, args] }
|
695
|
-
|
696
|
-
# match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
|
697
|
-
arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
|
698
|
-
# d { [:initialize, :arg_keywords, arg_keywords] }
|
699
|
-
|
700
|
-
# set up initial values with ~clones~ of specified values (so not shared between instances)
|
701
|
-
init_values = get_init_values
|
702
|
-
|
703
|
-
# add to start of key_values array (so can be overridden by params)
|
704
|
-
key_values.unshift(init_values)
|
705
|
-
|
706
|
-
# merge all hash args into one
|
707
|
-
key_values = key_values.inject(arg_keywords) { |hash, item| hash.merge(item)}
|
708
|
-
|
709
|
-
# convert key names to symbols
|
710
|
-
key_values = key_values.inject({}) {|h, (k, v)| h[k.to_sym] = v; h}
|
711
|
-
Doodle::Debug.d { [:initialize_from_hash, :key_values2, key_values, :args2, args] }
|
712
|
-
|
713
|
-
# create attributes
|
714
|
-
key_values.keys.each do |key|
|
715
|
-
Doodle::Debug.d { [:initialize_from_hash, :setting, key, key_values[key]] }
|
716
|
-
if respond_to?(key)
|
717
|
-
send(key, key_values[key])
|
718
|
-
else
|
719
|
-
# raise error if not defined
|
720
|
-
handle_error key, Doodle::UnknownAttributeError, "Unknown attribute '#{key}' #{key_values[key].inspect}"
|
721
|
-
end
|
722
|
-
end
|
723
|
-
end
|
724
|
-
end
|
725
|
-
#private :initialize_from_hash
|
726
719
|
|
727
720
|
# return true if instance variable +name+ defined
|
728
721
|
def ivar_defined?(name)
|
@@ -733,38 +726,48 @@ module Doodle
|
|
733
726
|
# validate this object by applying all validations in sequence
|
734
727
|
# - if all == true, validate all attributes, e.g. when loaded from YAML, else validate at object level only
|
735
728
|
def validate!(all = true)
|
736
|
-
|
729
|
+
Doodle::Debug.d { [:validate!, all, caller] }
|
737
730
|
if all
|
738
731
|
clear_errors
|
739
732
|
end
|
740
|
-
#Doodle::Debug.d { [:validate!, self] }
|
741
|
-
#Doodle::Debug.d { [:validate!, self, __doodle__.validation_on] }
|
742
733
|
if __doodle__.validation_on
|
743
734
|
if self.class == Class
|
744
|
-
attribs =
|
735
|
+
attribs = class_attributes
|
736
|
+
Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
|
745
737
|
else
|
746
738
|
attribs = attributes
|
739
|
+
Doodle::Debug.d { [:validate!, "using instance_attributes", attributes] }
|
747
740
|
end
|
748
|
-
#pp [:validate!, self, self.class, attributes]
|
749
741
|
attribs.each do |name, att|
|
750
742
|
# treat default as special case
|
751
|
-
if
|
752
|
-
|
753
|
-
|
754
|
-
handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
|
743
|
+
if att.default_defined?
|
744
|
+
Doodle::Debug.d { [:validate!, "default_defined - breaking" ]}
|
745
|
+
break
|
755
746
|
end
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
747
|
+
ivar_name = "@#{att.name}"
|
748
|
+
if instance_variable_defined?(ivar_name)
|
749
|
+
# if all == true, reset values so conversions and
|
750
|
+
# validations are applied to raw instance variables
|
751
|
+
# e.g. when loaded from YAML
|
752
|
+
if all
|
753
|
+
Doodle::Debug.d { [:validate!, :sending, att.name, instance_variable_get(ivar_name) ] }
|
754
|
+
__send__(att.name, instance_variable_get(ivar_name))
|
755
|
+
end
|
756
|
+
elsif self.class != Class
|
757
|
+
handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
|
761
758
|
end
|
762
759
|
end
|
763
760
|
# now apply instance level validations
|
761
|
+
|
762
|
+
Doodle::Debug.d { [:validate!, "validations", validations ]}
|
764
763
|
validations.each do |v|
|
765
764
|
Doodle::Debug.d { [:validate!, self, v ] }
|
766
|
-
|
767
|
-
|
765
|
+
begin
|
766
|
+
if !instance_eval(&v.block)
|
767
|
+
handle_error self, ValidationError, "#{ self.class } must #{ v.message }", [caller[-1]]
|
768
|
+
end
|
769
|
+
rescue Exception => e
|
770
|
+
handle_error self, ValidationError, e.to_s, [caller[-1]]
|
768
771
|
end
|
769
772
|
end
|
770
773
|
end
|
@@ -787,20 +790,66 @@ module Doodle
|
|
787
790
|
v
|
788
791
|
end
|
789
792
|
|
793
|
+
# helper function to initialize from hash - this is safe to use
|
794
|
+
# after initialization (validate! is called if this method is
|
795
|
+
# called after initialization)
|
796
|
+
def initialize_from_hash(*args)
|
797
|
+
defer_validation do
|
798
|
+
# hash initializer
|
799
|
+
# separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
|
800
|
+
key_values, args = args.partition{ |x| x.kind_of?(Hash)}
|
801
|
+
Doodle::Debug.d { [self.class, :initialize_from_hash, :key_values, key_values, :args, args] }
|
802
|
+
|
803
|
+
# match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
|
804
|
+
arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
|
805
|
+
|
806
|
+
# set up initial values with ~clones~ of specified values (so not shared between instances)
|
807
|
+
init_values = get_init_values
|
808
|
+
|
809
|
+
# add to start of key_values array (so can be overridden by params)
|
810
|
+
key_values.unshift(init_values)
|
811
|
+
|
812
|
+
# merge all hash args into one
|
813
|
+
key_values = key_values.inject(arg_keywords) { |hash, item| hash.merge(item)}
|
814
|
+
|
815
|
+
# convert key names to symbols
|
816
|
+
key_values = key_values.inject({}) {|h, (k, v)| h[k.to_sym] = v; h}
|
817
|
+
Doodle::Debug.d { [self.class, :initialize_from_hash, :key_values2, key_values, :args2, args] }
|
818
|
+
|
819
|
+
# create attributes
|
820
|
+
key_values.keys.each do |key|
|
821
|
+
Doodle::Debug.d { [self.class, :initialize_from_hash, :setting, key, key_values[key]] }
|
822
|
+
if respond_to?(key)
|
823
|
+
__send__(key, key_values[key])
|
824
|
+
else
|
825
|
+
# raise error if not defined
|
826
|
+
handle_error key, Doodle::UnknownAttributeError, "Unknown attribute '#{key}' #{key_values[key].inspect}"
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|
830
|
+
end
|
831
|
+
#private :initialize_from_hash
|
832
|
+
|
833
|
+
# return containing object (set during initialization)
|
834
|
+
def parent
|
835
|
+
__doodle__.parent
|
836
|
+
end
|
837
|
+
|
790
838
|
# object can be initialized from a mixture of positional arguments,
|
791
839
|
# hash of keyword value pairs and a block which is instance_eval'd
|
792
840
|
def initialize(*args, &block)
|
841
|
+
built_in = Doodle::BuiltIns::BUILTINS.select{ |x| self.kind_of?(x) }.first
|
842
|
+
if built_in
|
843
|
+
super
|
844
|
+
end
|
793
845
|
__doodle__.validation_on = true
|
794
|
-
|
846
|
+
__doodle__.parent = Doodle.context[-1]
|
795
847
|
Doodle.context.push(self)
|
796
848
|
defer_validation do
|
797
|
-
# d { [:initialize, self.to_s, args, block] }
|
798
849
|
initialize_from_hash(*args)
|
799
|
-
# d { [:initialize, self.to_s, args, block, :calling_block] }
|
800
850
|
instance_eval(&block) if block_given?
|
801
851
|
end
|
802
852
|
Doodle.context.pop
|
803
|
-
#p [:Doodle_context_pop, ]
|
804
853
|
end
|
805
854
|
|
806
855
|
end
|
@@ -822,35 +871,36 @@ module Doodle
|
|
822
871
|
# etc.
|
823
872
|
module Factory
|
824
873
|
RX_IDENTIFIER = /^[A-Za-z_][A-Za-z_0-9]+\??$/
|
825
|
-
class << self
|
874
|
+
class << self
|
826
875
|
# create a factory function in appropriate module for the specified class
|
827
876
|
def factory(konst)
|
828
|
-
#p [:factory, name]
|
829
877
|
name = konst.to_s
|
830
878
|
names = name.split(/::/)
|
831
879
|
name = names.pop
|
832
880
|
if names.empty?
|
833
881
|
# top level class - should be available to all
|
834
882
|
klass = Object
|
835
|
-
|
836
|
-
|
883
|
+
method_defined = begin
|
884
|
+
method(name)
|
885
|
+
true
|
886
|
+
rescue
|
887
|
+
false
|
888
|
+
end
|
889
|
+
|
890
|
+
if !method_defined && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING) && name =~ Factory::RX_IDENTIFIER
|
837
891
|
eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
|
838
892
|
end
|
839
893
|
else
|
840
894
|
klass = names.inject(self) {|c, n| c.const_get(n)}
|
841
|
-
|
842
|
-
#p [:names, klass, mklass]
|
843
|
-
# TODO: check how many times this is being called
|
895
|
+
# todo[check how many times this is being called]
|
844
896
|
if !klass.respond_to?(name) && name =~ Factory::RX_IDENTIFIER
|
845
|
-
klass.
|
897
|
+
klass.module_eval("def self.#{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
|
846
898
|
end
|
847
899
|
end
|
848
|
-
#p [:factory, mklass, klass, src]
|
849
900
|
end
|
850
901
|
|
851
902
|
# inherit the factory function capability
|
852
903
|
def included(other)
|
853
|
-
#p [:factory, :included, self, other ]
|
854
904
|
super
|
855
905
|
# make +factory+ method available
|
856
906
|
factory other
|
@@ -858,12 +908,11 @@ module Doodle
|
|
858
908
|
end
|
859
909
|
end
|
860
910
|
|
861
|
-
# Include Doodle::
|
911
|
+
# Include Doodle::Core if you want to derive from another class
|
862
912
|
# but still get Doodle goodness in your class (including Factory
|
863
913
|
# methods).
|
864
|
-
module
|
914
|
+
module Core
|
865
915
|
def self.included(other)
|
866
|
-
#p [:Helper, :included, self, other ]
|
867
916
|
super
|
868
917
|
other.module_eval {
|
869
918
|
extend Embrace
|
@@ -872,17 +921,27 @@ module Doodle
|
|
872
921
|
}
|
873
922
|
end
|
874
923
|
end
|
924
|
+
# deprecated
|
925
|
+
Helper = Core
|
875
926
|
|
876
|
-
#
|
927
|
+
# deprecated
|
877
928
|
class Base
|
878
|
-
include
|
929
|
+
include Core
|
879
930
|
end
|
931
|
+
include Core
|
932
|
+
end
|
880
933
|
|
881
|
-
|
882
|
-
|
934
|
+
class Doodle
|
935
|
+
# Attribute is itself a Doodle object that is created by #has and
|
936
|
+
# added to the #attributes collection in an object's DoodleInfo
|
937
|
+
#
|
938
|
+
# It is used to provide a context for defining #must and #from rules
|
939
|
+
#
|
940
|
+
class Attribute < Doodle
|
941
|
+
# todo[want to design Attribute so it's extensible, e.g. to specific datatypes & built-in validations]
|
883
942
|
# must define these methods before using them in #has below
|
884
943
|
|
885
|
-
# bump off +validate!+ for Attributes - maybe better way of doing
|
944
|
+
# hack: bump off +validate!+ for Attributes - maybe better way of doing
|
886
945
|
# this however, without this, tries to validate Attribute to :kind
|
887
946
|
# specified, e.g. if you have
|
888
947
|
#
|
@@ -891,6 +950,8 @@ module Doodle
|
|
891
950
|
# it will fail because Attribute is not a kind of Date -
|
892
951
|
# obviously, I have to think about this some more :S
|
893
952
|
#
|
953
|
+
# at least, I could hand roll a custom validate! method for Attribute
|
954
|
+
#
|
894
955
|
def validate!(all = true)
|
895
956
|
end
|
896
957
|
|
@@ -920,9 +981,11 @@ module Doodle
|
|
920
981
|
s.to_sym
|
921
982
|
end
|
922
983
|
end
|
984
|
+
|
923
985
|
# default value (can be a block)
|
924
986
|
has :default, :default => nil
|
925
|
-
|
987
|
+
|
988
|
+
# initial value
|
926
989
|
has :init, :default => nil
|
927
990
|
|
928
991
|
end
|