doodle 0.1.9 → 0.2.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/History.txt +29 -10
- data/Manifest.txt +12 -0
- data/README.txt +5 -4
- data/lib/doodle.rb +227 -112
- data/lib/doodle/app.rb +25 -21
- data/lib/doodle/version.rb +2 -2
- data/lib/doodle/xml.rb +175 -0
- data/spec/arg_order_spec.rb +1 -1
- data/spec/block_init_spec.rb +1 -1
- data/spec/bugs_spec.rb +61 -29
- data/spec/collector_spec.rb +92 -25
- data/spec/defaults_spec.rb +13 -0
- data/spec/doodle_context_spec.rb +1 -1
- data/spec/doodle_spec.rb +4 -14
- data/spec/equality_spec.rb +118 -0
- data/spec/modules_spec.rb +67 -0
- data/spec/serialization_spec.rb +3 -3
- data/spec/spec.template +16 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/to_hash_spec.rb +12 -2
- data/spec/validation2_spec.rb +10 -10
- data/spec/validation_spec.rb +1 -1
- data/spec/xml_spec.rb +219 -0
- metadata +17 -2
data/History.txt
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
== 0.2.0 / 2009-02-08
|
2
|
+
- Features:
|
3
|
+
- object.doodle.values for easy access to array of attribute values
|
4
|
+
- object.default?(:name) returns true if :name has default and not been assigned
|
5
|
+
- equality
|
6
|
+
- doodles are equal if both have the same class and values
|
7
|
+
- comparability
|
8
|
+
- defines <=> (so doodles are sortable by default)
|
9
|
+
- XML serialization - require 'doodle/xml'
|
10
|
+
simple XML serialization using Doodle class names as tag
|
11
|
+
names. Works for me. YMMV :)
|
12
|
+
- to_xml
|
13
|
+
- from_xml
|
14
|
+
- reworked website using webby
|
15
|
+
|
16
|
+
- Bug fixes:
|
17
|
+
- all specs now pass in ruby 1.8.6, 1.9.1 and JRuby 1.1.6 (1 pending)
|
18
|
+
- renabled String collectors
|
19
|
+
|
1
20
|
== 0.1.9 / 2008-08-13
|
2
21
|
- Features:
|
3
22
|
- to_hash
|
@@ -9,15 +28,15 @@
|
|
9
28
|
class Animal
|
10
29
|
has :species
|
11
30
|
end
|
12
|
-
|
31
|
+
|
13
32
|
class Barn
|
14
33
|
has :animals, :collect => Animal
|
15
34
|
end
|
16
|
-
|
35
|
+
|
17
36
|
class Farm
|
18
37
|
has Barn
|
19
38
|
end
|
20
|
-
|
39
|
+
|
21
40
|
farm = Farm do
|
22
41
|
# this is new - will call Barn.new(&block)
|
23
42
|
barn do
|
@@ -25,9 +44,9 @@
|
|
25
44
|
animal 'pig'
|
26
45
|
end
|
27
46
|
end
|
28
|
-
|
47
|
+
|
29
48
|
Will not try this for an attribute with :abstract => true
|
30
|
-
|
49
|
+
|
31
50
|
- attributes now have :doc option
|
32
51
|
- attributes now have :abstract option - will not try to
|
33
52
|
auto-instantiate an object from this class
|
@@ -39,11 +58,11 @@
|
|
39
58
|
optionally recurse into child hashes
|
40
59
|
- symbolize_keys!(hash, recursive = false)
|
41
60
|
- stringify_keys!(hash, recursive = false)
|
42
|
-
|
61
|
+
|
43
62
|
- Experimental:
|
44
63
|
- Doodle::App for handlng command line application options
|
45
64
|
- doodle/datatypes - added more datatypes
|
46
|
-
|
65
|
+
|
47
66
|
- Bug fixes:
|
48
67
|
- fixed reversion in 0.1.8 which enabled full backtrace from within
|
49
68
|
doodle.rb
|
@@ -129,7 +148,7 @@
|
|
129
148
|
|
130
149
|
== 0.1.5 / 2008-05-06
|
131
150
|
- Bug fixes:
|
132
|
-
- fixed bug where defaults were preventing validation working when loading from
|
151
|
+
- fixed bug where defaults were preventing validation working when loading from
|
133
152
|
YAML and added spec
|
134
153
|
|
135
154
|
== 0.1.4 / 2008-05-06
|
@@ -159,7 +178,7 @@
|
|
159
178
|
- created a Google group: http://groups.google.com/group/ruby-doodle
|
160
179
|
- major change: changed Doodle from module to class so you now use
|
161
180
|
class Foo < Doodle
|
162
|
-
instead of
|
181
|
+
instead of
|
163
182
|
class Foo < Doodle::Base
|
164
183
|
- Doodle::Helper renamed Doodle::Core
|
165
184
|
- added #parent method - returns outer object in initialization blocks
|
@@ -234,7 +253,7 @@
|
|
234
253
|
|
235
254
|
- tidied up validation specs
|
236
255
|
|
237
|
-
- fixed dumb bug where validations were getting duplicated resulting from using push on the source array. D'oh!
|
256
|
+
- fixed dumb bug where validations were getting duplicated resulting from using push on the source array. D'oh!
|
238
257
|
|
239
258
|
- added Doodle::DoodleInfo.raise_exception_on_error - set to false to have errors collected only
|
240
259
|
without raising an exception (which is the default behaviour)
|
data/Manifest.txt
CHANGED
@@ -28,6 +28,7 @@ lib/doodle/datatypes.rb
|
|
28
28
|
lib/doodle/rfc822.rb
|
29
29
|
lib/doodle/utils.rb
|
30
30
|
lib/doodle/version.rb
|
31
|
+
lib/doodle/xml.rb
|
31
32
|
lib/molic_orderedhash.rb
|
32
33
|
log/debug.log
|
33
34
|
script/console
|
@@ -37,6 +38,7 @@ script/txt2html
|
|
37
38
|
setup.rb
|
38
39
|
spec/arg_order_spec.rb
|
39
40
|
spec/attributes_spec.rb
|
41
|
+
spec/block_init_spec.rb
|
40
42
|
spec/bugs_spec.rb
|
41
43
|
spec/class_spec.rb
|
42
44
|
spec/class_validation_spec.rb
|
@@ -46,21 +48,31 @@ spec/conversion_spec.rb
|
|
46
48
|
spec/defaults_spec.rb
|
47
49
|
spec/doodle_context_spec.rb
|
48
50
|
spec/doodle_spec.rb
|
51
|
+
spec/equality_spec.rb
|
49
52
|
spec/extra_args_spec.rb
|
50
53
|
spec/factory_spec.rb
|
51
54
|
spec/flatten_first_level_spec.rb
|
55
|
+
spec/from_spec.rb
|
56
|
+
spec/has_spec.rb
|
52
57
|
spec/inheritance_spec.rb
|
53
58
|
spec/init_spec.rb
|
59
|
+
spec/kind_spec.rb
|
60
|
+
spec/member_init_spec.rb
|
54
61
|
spec/new_doodle_spec.rb
|
62
|
+
spec/readonly_spec.rb
|
55
63
|
spec/required_spec.rb
|
56
64
|
spec/serialization_spec.rb
|
57
65
|
spec/singleton_spec.rb
|
58
66
|
spec/spec.opts
|
67
|
+
spec/spec.template
|
59
68
|
spec/spec_helper.rb
|
60
69
|
spec/specialized_attribute_class_spec.rb
|
61
70
|
spec/superclass_spec.rb
|
71
|
+
spec/symbolize_keys_spec.rb
|
72
|
+
spec/to_hash_spec.rb
|
62
73
|
spec/validation2_spec.rb
|
63
74
|
spec/validation_spec.rb
|
75
|
+
spec/xml_spec.rb
|
64
76
|
tasks/deployment.rake
|
65
77
|
tasks/environment.rake
|
66
78
|
tasks/rspec.rake
|
data/README.txt
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= doodle
|
2
2
|
|
3
|
-
* http://doodle.rubyforge.org
|
3
|
+
* Homepage: http://doodle.rubyforge.org
|
4
|
+
* Github repo: http://github.com/seanohalpin/doodle/tree/master
|
4
5
|
|
5
6
|
== DESCRIPTION:
|
6
7
|
|
@@ -11,8 +12,8 @@ Doodle is eco-friendly: it does not globally modify Object, Class or
|
|
11
12
|
Module, nor does it pollute instances with its own instance variables
|
12
13
|
(i.e. it plays nice with yaml).
|
13
14
|
|
14
|
-
Doodle has been tested with Ruby 1.8.6 and JRuby 1.1. It
|
15
|
-
|
15
|
+
Doodle has been tested with Ruby 1.8.6, 1.9.1 and JRuby 1.1. It has
|
16
|
+
not yet been tested with Rubinius.
|
16
17
|
|
17
18
|
Please feel free to post bug reports, feature requests, and any
|
18
19
|
comments or discussion topics to the doodle Google group:
|
@@ -144,7 +145,7 @@ http://groups.google.com/group/ruby-doodle
|
|
144
145
|
|
145
146
|
(The MIT License)
|
146
147
|
|
147
|
-
Copyright (c)
|
148
|
+
Copyright (c) 2007-2009 Sean O'Halpin
|
148
149
|
|
149
150
|
Permission is hereby granted, free of charge, to any person obtaining
|
150
151
|
a copy of this software and associated documentation files (the
|
data/lib/doodle.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
# doodle
|
2
2
|
# -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- vim: sw=2 ts=2
|
3
|
-
# Copyright (C) 2007-
|
3
|
+
# Copyright (C) 2007-2009 by Sean O'Halpin
|
4
4
|
# 2007-11-24 first version
|
5
5
|
# 2008-04-18 latest release 0.0.12
|
6
6
|
# 2008-05-07 0.1.6
|
7
7
|
# 2008-05-12 0.1.7
|
8
|
+
# 2009-02-26 0.2.0
|
9
|
+
# require Ruby 1.8.6 or higher
|
10
|
+
if RUBY_VERSION < '1.8.6'
|
11
|
+
raise Exception, "Sorry - doodle does not work with versions of Ruby below 1.8.6"
|
12
|
+
end
|
13
|
+
|
8
14
|
$:.unshift(File.dirname(__FILE__)) unless
|
9
15
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
10
16
|
|
11
17
|
if RUBY_VERSION < '1.9.0'
|
12
|
-
require 'molic_orderedhash' #
|
18
|
+
require 'molic_orderedhash' # TODO: replace this with own (required functions only) version
|
13
19
|
else
|
14
20
|
# 1.9+ hashes are ordered by default
|
15
21
|
class Doodle
|
@@ -17,45 +23,6 @@ else
|
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
|
-
# require Ruby 1.8.6 or higher
|
21
|
-
if RUBY_VERSION < '1.8.6'
|
22
|
-
raise Exception, "Sorry - doodle does not work with versions of Ruby below 1.8.6"
|
23
|
-
end
|
24
|
-
|
25
|
-
#
|
26
|
-
# instance_exec for ruby 1.8 by Mauricio Fernandez
|
27
|
-
# http://eigenclass.org/hiki.rb?bounded+space+instance_exec
|
28
|
-
# thread-safe and handles frozen objects in bounded space
|
29
|
-
#
|
30
|
-
# (tag "ruby instance_exec")
|
31
|
-
#
|
32
|
-
if !Object.respond_to?(:instance_exec)
|
33
|
-
class Object
|
34
|
-
module InstanceExecHelper; end
|
35
|
-
include InstanceExecHelper
|
36
|
-
def instance_exec(*args, &block)
|
37
|
-
begin
|
38
|
-
old_critical, Thread.critical = Thread.critical, true
|
39
|
-
n = 0
|
40
|
-
methods = InstanceExecHelper.instance_methods
|
41
|
-
# this in order to make the lookup O(1), and name generation O(n) on the
|
42
|
-
# number of nested/concurrent instance_exec calls instead of O(n**2)
|
43
|
-
table = Hash[*methods.zip(methods).flatten]
|
44
|
-
n += 1 while table.has_key?(mname="__instance_exec#{n}")
|
45
|
-
ensure
|
46
|
-
Thread.critical = old_critical
|
47
|
-
end
|
48
|
-
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
49
|
-
begin
|
50
|
-
ret = send(mname, *args)
|
51
|
-
ensure
|
52
|
-
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
|
53
|
-
end
|
54
|
-
ret
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
26
|
require 'yaml'
|
60
27
|
|
61
28
|
# *doodle* is my attempt at an eco-friendly metaprogramming framework that does not
|
@@ -79,6 +46,32 @@ class Doodle
|
|
79
46
|
end
|
80
47
|
end
|
81
48
|
|
49
|
+
# two doodles of the same class with the same attribute values are
|
50
|
+
# considered equal
|
51
|
+
module Equality
|
52
|
+
def eql?(o)
|
53
|
+
# p [:comparing, self.class, o.class, self.class == o.class]
|
54
|
+
# p [:values, self.doodle.values, o.doodle.values, self.doodle.values == o.doodle.values]
|
55
|
+
# p [:attributes, doodle.attributes.map { |k, a| [k, send(k).==(o.send(k))] }]
|
56
|
+
res = self.class == o.class &&
|
57
|
+
#self.doodle.values == o.doodle.values
|
58
|
+
# short circuit comparison
|
59
|
+
doodle.attributes.all? { |k, a| send(k).==(o.send(k)) }
|
60
|
+
# p [:res, res]
|
61
|
+
res
|
62
|
+
end
|
63
|
+
def ==(o)
|
64
|
+
eql?(o)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# doodles are compared (sorted) on values
|
69
|
+
module Comparable
|
70
|
+
def <=>(o)
|
71
|
+
doodle.values <=> o.doodle.values
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
82
75
|
# debugging utilities
|
83
76
|
module Debug
|
84
77
|
class << self
|
@@ -99,13 +92,26 @@ class Doodle
|
|
99
92
|
class << self
|
100
93
|
# Unnest arrays by one level of nesting, e.g. [1, [[2], 3]] => [1, [2], 3].
|
101
94
|
def flatten_first_level(enum)
|
102
|
-
enum.inject([]) {|arr, i|
|
95
|
+
enum.inject([]) {|arr, i|
|
96
|
+
if i.kind_of?(Array)
|
97
|
+
arr.push(*i)
|
98
|
+
else
|
99
|
+
arr.push(i)
|
100
|
+
end
|
101
|
+
}
|
103
102
|
end
|
104
103
|
# from facets/string/case.rb, line 80
|
105
104
|
def snake_case(camel_cased_word)
|
106
|
-
|
105
|
+
# if all caps, just downcase it
|
106
|
+
if camel_cased_word =~ /^[A-Z]+$/
|
107
|
+
camel_cased_word.downcase
|
108
|
+
else
|
109
|
+
camel_cased_word.to_s.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
110
|
+
end
|
107
111
|
end
|
108
|
-
# resolve a constant of the form Some::Class::Or::Module
|
112
|
+
# resolve a constant of the form Some::Class::Or::Module -
|
113
|
+
# doesn't work with constants defined in anonymous
|
114
|
+
# classes/modules
|
109
115
|
def const_resolve(constant)
|
110
116
|
constant.to_s.split(/::/).reject{|x| x.empty?}.inject(Object) { |prev, this| prev.const_get(this) }
|
111
117
|
end
|
@@ -117,17 +123,19 @@ class Doodle
|
|
117
123
|
# - updates target hash
|
118
124
|
# - optionally recurse into child hashes
|
119
125
|
def normalize_keys!(hash, recursive = false, method = :to_sym)
|
120
|
-
hash.
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
if
|
125
|
-
v
|
126
|
-
|
127
|
-
|
126
|
+
if hash.kind_of?(Hash)
|
127
|
+
hash.keys.each do |key|
|
128
|
+
normalized_key = key.respond_to?(method) ? key.send(method) : key
|
129
|
+
v = hash.delete(key)
|
130
|
+
if recursive
|
131
|
+
if v.kind_of?(Hash)
|
132
|
+
v = normalize_keys!(v, recursive, method)
|
133
|
+
elsif v.kind_of?(Array)
|
134
|
+
v = v.map{ |x| normalize_keys!(x, recursive, method) }
|
135
|
+
end
|
128
136
|
end
|
137
|
+
hash[normalized_key] = v
|
129
138
|
end
|
130
|
-
hash[normalized_key] = v
|
131
139
|
end
|
132
140
|
hash
|
133
141
|
end
|
@@ -389,6 +397,13 @@ class Doodle
|
|
389
397
|
results
|
390
398
|
end
|
391
399
|
|
400
|
+
# returns array of values
|
401
|
+
# - if tf == true, returns all inherited values (default)
|
402
|
+
# - if tf == false, returns only those values defined in current object
|
403
|
+
def values(tf = true)
|
404
|
+
attributes(tf).map{ |k, a| @this.send(k)}
|
405
|
+
end
|
406
|
+
|
392
407
|
# return class level attributes
|
393
408
|
def class_attributes
|
394
409
|
attrs = Doodle::OrderedHash.new
|
@@ -591,6 +606,18 @@ class Doodle
|
|
591
606
|
include SelfClass
|
592
607
|
include SmokeAndMirrors
|
593
608
|
|
609
|
+
# NOTE: can't do either of these
|
610
|
+
|
611
|
+
# include Equality
|
612
|
+
# include Comparable
|
613
|
+
|
614
|
+
# def self.included(other)
|
615
|
+
# other.module_eval {
|
616
|
+
# include Equality
|
617
|
+
# include Comparable
|
618
|
+
# }
|
619
|
+
# end
|
620
|
+
|
594
621
|
# this is the only way to get at internal values. Note: this is
|
595
622
|
# initialized on the fly rather than in #initialize because
|
596
623
|
# classes and singletons don't call #initialize
|
@@ -635,7 +662,7 @@ class Doodle
|
|
635
662
|
|
636
663
|
# either get an attribute value (if no args given) or set it
|
637
664
|
# (using args and/or block)
|
638
|
-
#
|
665
|
+
# FIXME: move
|
639
666
|
def getter_setter(name, *args, &block)
|
640
667
|
#p [:getter_setter, name]
|
641
668
|
name = name.to_sym
|
@@ -650,39 +677,43 @@ class Doodle
|
|
650
677
|
private :getter_setter
|
651
678
|
|
652
679
|
# get an attribute by name - return default if not otherwise defined
|
653
|
-
#
|
680
|
+
# FIXME: init deferred blocks are not getting resolved in all cases
|
654
681
|
def _getter(name, &block)
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
682
|
+
begin
|
683
|
+
#p [:_getter, name]
|
684
|
+
ivar = "@#{name}"
|
685
|
+
if instance_variable_defined?(ivar)
|
686
|
+
#p [:_getter, :instance_variable_defined, name, ivar, instance_variable_get(ivar)]
|
687
|
+
instance_variable_get(ivar)
|
688
|
+
else
|
689
|
+
# handle default
|
690
|
+
# Note: use :init => value to cover cases where defaults don't work
|
691
|
+
# (e.g. arrays that disappear when you go out of scope)
|
692
|
+
att = __doodle__.lookup_attribute(name)
|
693
|
+
# special case for class/singleton :init
|
694
|
+
if att && att.optional?
|
695
|
+
optional_value = att.init_defined? ? att.init : att.default
|
696
|
+
#p [:optional_value, optional_value]
|
697
|
+
case optional_value
|
698
|
+
when DeferredBlock
|
699
|
+
#p [:deferred_block]
|
700
|
+
v = instance_eval(&optional_value.block)
|
701
|
+
when Proc
|
702
|
+
v = instance_eval(&optional_value)
|
703
|
+
else
|
704
|
+
v = optional_value
|
705
|
+
end
|
706
|
+
if att.init_defined?
|
707
|
+
_setter(name, v)
|
708
|
+
end
|
709
|
+
v
|
675
710
|
else
|
676
|
-
|
677
|
-
|
678
|
-
if att.init_defined?
|
679
|
-
_setter(name, v)
|
711
|
+
# This is an internal error (i.e. shouldn't happen)
|
712
|
+
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined", Doodle::Utils.doodle_caller
|
680
713
|
end
|
681
|
-
v
|
682
|
-
else
|
683
|
-
# This is an internal error (i.e. shouldn't happen)
|
684
|
-
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined", Doodle::Utils.doodle_caller
|
685
714
|
end
|
715
|
+
rescue Object => e
|
716
|
+
__doodle__.handle_error name, e, e.to_s, Doodle::Utils.doodle_caller
|
686
717
|
end
|
687
718
|
end
|
688
719
|
private :_getter
|
@@ -690,6 +721,7 @@ class Doodle
|
|
690
721
|
def after_update(params)
|
691
722
|
end
|
692
723
|
|
724
|
+
# set an instance variable by symbolic name and call after_update if changed
|
693
725
|
def ivar_set(name, *args)
|
694
726
|
ivar = "@#{name}"
|
695
727
|
if instance_variable_defined?(ivar)
|
@@ -707,11 +739,10 @@ class Doodle
|
|
707
739
|
private :ivar_set
|
708
740
|
|
709
741
|
# set an attribute by name - apply validation if defined
|
710
|
-
#
|
742
|
+
# FIXME: move
|
711
743
|
def _setter(name, *args, &block)
|
712
744
|
##DBG: Doodle::Debug.d { [:_setter, name, args] }
|
713
745
|
#p [:_setter, name, *args]
|
714
|
-
#ivar = "@#{name}"
|
715
746
|
att = __doodle__.lookup_attribute(name)
|
716
747
|
if att && doodle.validation_on && att.readonly
|
717
748
|
raise Doodle::ReadOnlyError, "Trying to set a readonly attribute: #{att.name}", Doodle::Utils.doodle_caller
|
@@ -730,7 +761,6 @@ class Doodle
|
|
730
761
|
# this is used by init do ... block
|
731
762
|
args.unshift(DeferredBlock.new(block))
|
732
763
|
end
|
733
|
-
#elsif
|
734
764
|
end
|
735
765
|
if att # = __doodle__.lookup_attribute(name)
|
736
766
|
if att.kind && !att.abstract && klass = att.kind.first
|
@@ -744,9 +774,7 @@ class Doodle
|
|
744
774
|
end
|
745
775
|
end
|
746
776
|
end
|
747
|
-
# args = [klass.new(*args, &block)] ##DBG: Doodle::Debug.d { [:_setter, name, args] }
|
748
777
|
#p [:_setter, :got_att1, name, ivar, *args]
|
749
|
-
# v = instance_variable_set(ivar, att.validate(self, *args))
|
750
778
|
v = ivar_set(name, att.validate(self, *args))
|
751
779
|
|
752
780
|
#p [:_setter, :got_att2, name, ivar, :value, v]
|
@@ -754,7 +782,6 @@ class Doodle
|
|
754
782
|
else
|
755
783
|
#p [:_setter, :no_att, name, *args]
|
756
784
|
##DBG: Doodle::Debug.d { [:_setter, "no attribute"] }
|
757
|
-
# v = instance_variable_set(ivar, *args)
|
758
785
|
v = ivar_set(name, *args)
|
759
786
|
end
|
760
787
|
validate!(false)
|
@@ -798,7 +825,7 @@ class Doodle
|
|
798
825
|
end
|
799
826
|
|
800
827
|
# convert a value according to conversion rules
|
801
|
-
#
|
828
|
+
# FIXME: move
|
802
829
|
def convert(owner, *args)
|
803
830
|
#pp( { :convert => 1, :owner => owner, :args => args, :conversions => __doodle__.conversions } )
|
804
831
|
begin
|
@@ -807,7 +834,6 @@ class Doodle
|
|
807
834
|
if (converter = __doodle__.conversions[value.class])
|
808
835
|
#p [:convert, 3, value, self, caller]
|
809
836
|
value = converter[value]
|
810
|
-
#value = instance_exec(value, &converter)
|
811
837
|
#!p [:convert, 4, value]
|
812
838
|
else
|
813
839
|
#!p [:convert, 5, value]
|
@@ -825,7 +851,6 @@ class Doodle
|
|
825
851
|
if converter = __doodle__.conversions[converter_class]
|
826
852
|
#!p [:convert, 11, converter]
|
827
853
|
value = converter[value]
|
828
|
-
#value = instance_exec(value, &converter)
|
829
854
|
#!p [:convert, 12, value]
|
830
855
|
end
|
831
856
|
else
|
@@ -838,7 +863,6 @@ class Doodle
|
|
838
863
|
if converter = mappable_kind.doodle.conversions[value.class]
|
839
864
|
#!p [:convert, 15, value, mappable_kind, args]
|
840
865
|
value = converter[value]
|
841
|
-
#value = instance_exec(value, &converter)
|
842
866
|
break
|
843
867
|
else
|
844
868
|
#!p [:convert, 16, :no_conversion_for, value.class]
|
@@ -973,6 +997,12 @@ class Doodle
|
|
973
997
|
end
|
974
998
|
private :ivar_defined?
|
975
999
|
|
1000
|
+
# return true if attribute has default defined and not yet been
|
1001
|
+
# assigned to (i.e. still has default value)
|
1002
|
+
def default?(name)
|
1003
|
+
doodle.attributes[name.to_sym].optional? && !ivar_defined?(name)
|
1004
|
+
end
|
1005
|
+
|
976
1006
|
# validate this object by applying all validations in sequence
|
977
1007
|
# - if all == true, validate all attributes, e.g. when loaded from YAML, else validate at object level only
|
978
1008
|
def validate!(all = true)
|
@@ -1002,7 +1032,7 @@ class Doodle
|
|
1002
1032
|
##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
|
1003
1033
|
next
|
1004
1034
|
elsif self.class != Class
|
1005
|
-
__doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", Doodle::Utils.doodle_caller
|
1035
|
+
__doodle__.handle_error name, Doodle::ValidationError, "#{self.kind_of?(Class) ? self : self.class } missing required attribute '#{name}'", Doodle::Utils.doodle_caller
|
1006
1036
|
end
|
1007
1037
|
end
|
1008
1038
|
|
@@ -1044,7 +1074,7 @@ class Doodle
|
|
1044
1074
|
#p [:doodle_parent, __doodle__.parent]
|
1045
1075
|
end
|
1046
1076
|
|
1047
|
-
# create 'pure' hash of scalars only from attributes - hacky but works
|
1077
|
+
# create 'pure' hash of scalars only from attributes - hacky but works (kinda)
|
1048
1078
|
def to_hash
|
1049
1079
|
Doodle::Utils.symbolize_keys!(YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }, true)
|
1050
1080
|
#begin
|
@@ -1074,17 +1104,65 @@ class Doodle
|
|
1074
1104
|
# end
|
1075
1105
|
# stimpy = Dog(:name => 'Stimpy')
|
1076
1106
|
# etc.
|
1107
|
+
module Utils
|
1108
|
+
# normalize a name to contain only legal characters for a Ruby
|
1109
|
+
# constant
|
1110
|
+
def self.normalize_const(const)
|
1111
|
+
const.to_s.gsub(/[^A-Za-z_0-9]/, '')
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
# lookup a constant along the module nesting path
|
1115
|
+
def const_lookup(const, context = self)
|
1116
|
+
#p [:const_lookup, const, context]
|
1117
|
+
const = Utils.normalize_const(const)
|
1118
|
+
result = nil
|
1119
|
+
if !context.kind_of?(Module)
|
1120
|
+
context = context.class
|
1121
|
+
end
|
1122
|
+
klasses = context.to_s.split(/::/)
|
1123
|
+
#p klasses
|
1124
|
+
|
1125
|
+
path = []
|
1126
|
+
0.upto(klasses.size - 1) do |i|
|
1127
|
+
path << Doodle::Utils.const_resolve(klasses[0..i].join('::'))
|
1128
|
+
end
|
1129
|
+
path = (path.reverse + context.ancestors).flatten
|
1130
|
+
#p [:const, context, path]
|
1131
|
+
path.each do |ctx|
|
1132
|
+
#p [:checking, ctx]
|
1133
|
+
if ctx.const_defined?(const)
|
1134
|
+
result = ctx.const_get(const)
|
1135
|
+
break
|
1136
|
+
end
|
1137
|
+
end
|
1138
|
+
raise NameError, "Uninitialized constant #{const} in context #{context}" if result.nil?
|
1139
|
+
result
|
1140
|
+
end
|
1141
|
+
module_function :const_lookup
|
1142
|
+
end
|
1143
|
+
|
1077
1144
|
module Factory
|
1078
1145
|
RX_IDENTIFIER = /^[A-Za-z_][A-Za-z_0-9]+\??$/
|
1079
|
-
class << self
|
1146
|
+
#class << self
|
1080
1147
|
# create a factory function in appropriate module for the specified class
|
1081
|
-
def factory(konst)
|
1148
|
+
def self.factory(konst)
|
1149
|
+
#p [:factory, :ancestors, konst, konst.ancestors]
|
1150
|
+
#p [:factory, :lookup, Module.nesting]
|
1082
1151
|
name = konst.to_s
|
1152
|
+
#p [:factory, :name, name]
|
1153
|
+
anon_class = false
|
1154
|
+
if name =~ /#<Class:0x[a-fA-F0-9]+>::/
|
1155
|
+
#p [:factory_anon_class, name]
|
1156
|
+
anon_class = true
|
1157
|
+
end
|
1083
1158
|
names = name.split(/::/)
|
1084
1159
|
name = names.pop
|
1085
|
-
|
1160
|
+
# TODO: the code below is almost the same - refactor
|
1161
|
+
#p [:factory, :names, names, name]
|
1162
|
+
if names.empty? && !anon_class
|
1163
|
+
#p [:factory, :top_level_class]
|
1086
1164
|
# top level class - should be available to all
|
1087
|
-
|
1165
|
+
parent_class = Object
|
1088
1166
|
method_defined = begin
|
1089
1167
|
method(name)
|
1090
1168
|
true
|
@@ -1092,25 +1170,48 @@ class Doodle
|
|
1092
1170
|
false
|
1093
1171
|
end
|
1094
1172
|
|
1095
|
-
if name =~ Factory::RX_IDENTIFIER && !method_defined && !
|
1173
|
+
if name =~ Factory::RX_IDENTIFIER && !method_defined && !parent_class.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING)
|
1096
1174
|
eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
|
1097
1175
|
end
|
1098
1176
|
else
|
1099
|
-
|
1100
|
-
|
1101
|
-
if
|
1102
|
-
|
1177
|
+
#p [:factory, :other_level_class]
|
1178
|
+
parent_class = Object
|
1179
|
+
if !anon_class
|
1180
|
+
parent_class = names.inject(parent_class) {|c, n| c.const_get(n)}
|
1181
|
+
#p [:factory, :parent_class, parent_class]
|
1182
|
+
if name =~ Factory::RX_IDENTIFIER && !parent_class.respond_to?(name)
|
1183
|
+
parent_class.module_eval("def self.#{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
|
1184
|
+
end
|
1185
|
+
else
|
1186
|
+
# NOTE: ruby 1.9.1 specific
|
1187
|
+
parent_class_name = names.join('::')
|
1188
|
+
#p [:factory, :parent_class_name, parent_class_name]
|
1189
|
+
#p [:parent_class_name, parent_class_name]
|
1190
|
+
# FIXME: this is truly horrible...
|
1191
|
+
hex_object_id = parent_class_name.match(/:(0x[a-zA-Z0-9]+)/)[1]
|
1192
|
+
oid = hex_object_id.to_i(16) >> 1
|
1193
|
+
# p [:object_id, oid, hex_object_id, hex_object_id.to_i(16) >> 1]
|
1194
|
+
parent_class = ObjectSpace._id2ref(oid)
|
1195
|
+
|
1196
|
+
#p [:parent_object_id, parent_class.object_id, names, parent_class, parent_class_name, parent_class.name]
|
1197
|
+
# p [:names, :oid, "%x" % (oid << 1), :konst, konst, :pc, parent_class, :names, names, :self, self]
|
1198
|
+
if name =~ Factory::RX_IDENTIFIER && !parent_class.respond_to?(name)
|
1199
|
+
#p [:context, context]
|
1200
|
+
parent_class.module_eval("def #{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
|
1201
|
+
end
|
1103
1202
|
end
|
1203
|
+
# TODO: check how many times this is being called
|
1104
1204
|
end
|
1105
1205
|
end
|
1106
1206
|
|
1107
1207
|
# inherit the factory function capability
|
1108
|
-
def included(other)
|
1208
|
+
def self.included(other)
|
1209
|
+
#p [:included, other]
|
1109
1210
|
super
|
1110
1211
|
# make +factory+ method available
|
1111
1212
|
factory other
|
1112
1213
|
end
|
1113
|
-
end
|
1214
|
+
#end
|
1114
1215
|
end
|
1115
1216
|
|
1116
1217
|
# Include Doodle::Core if you want to derive from another class
|
@@ -1120,6 +1221,9 @@ class Doodle
|
|
1120
1221
|
def self.included(other)
|
1121
1222
|
super
|
1122
1223
|
other.module_eval {
|
1224
|
+
# FIXME: this is getting a bit arbitrary
|
1225
|
+
include Doodle::Equality
|
1226
|
+
include Doodle::Comparable
|
1123
1227
|
extend Embrace
|
1124
1228
|
embrace BaseMethods
|
1125
1229
|
include Factory
|
@@ -1127,10 +1231,6 @@ class Doodle
|
|
1127
1231
|
end
|
1128
1232
|
end
|
1129
1233
|
|
1130
|
-
# wierd 1.9 shit
|
1131
|
-
class IAmNotUsedBut1_9GoesIntoAnInfiniteRegressInInspectIfIAmNotDefined
|
1132
|
-
include Core
|
1133
|
-
end
|
1134
1234
|
include Core
|
1135
1235
|
end
|
1136
1236
|
|
@@ -1190,7 +1290,9 @@ class Doodle
|
|
1190
1290
|
collector_class = collector.to_s
|
1191
1291
|
#p [:collector_klass, collector_klass]
|
1192
1292
|
collector_name = Utils.snake_case(collector_class.split(/::/).last)
|
1193
|
-
#p [:collector_name, collector_name]
|
1293
|
+
#p [:collector_name, collector_class, collector_name]
|
1294
|
+
# FIXME: sanitize class name (make this a Utils function)
|
1295
|
+
collector_class = collector_class.gsub(/#<Class:0x[a-fA-F0-9]+>::/, '')
|
1194
1296
|
if collector_class !~ /^[A-Z]/
|
1195
1297
|
collector_class = nil
|
1196
1298
|
end
|
@@ -1289,10 +1391,13 @@ class Doodle
|
|
1289
1391
|
end
|
1290
1392
|
def resolve_value(value)
|
1291
1393
|
if value.kind_of?(collector_class)
|
1394
|
+
#p [:resolve_value, :value, value]
|
1292
1395
|
value
|
1293
1396
|
elsif collector_class.__doodle__.conversions.key?(value.class)
|
1397
|
+
#p [:resolve_value, :collector_class_from, value]
|
1294
1398
|
collector_class.from(value)
|
1295
1399
|
else
|
1400
|
+
#p [:resolve_value, :collector_class_new, value]
|
1296
1401
|
collector_class.new(value)
|
1297
1402
|
end
|
1298
1403
|
end
|
@@ -1307,11 +1412,19 @@ class Doodle
|
|
1307
1412
|
end
|
1308
1413
|
end
|
1309
1414
|
from Enumerable do |enum|
|
1415
|
+
#p [:enum, Enumerable]
|
1310
1416
|
resolve_collector_class
|
1311
|
-
|
1417
|
+
# this is not very elegant but String is a classified as an
|
1418
|
+
# Enumerable in 1.8.x (but behaves differently)
|
1419
|
+
if enum.kind_of?(String) && self.init.kind_of?(String)
|
1420
|
+
post_process( resolve_value(enum) )
|
1421
|
+
else
|
1422
|
+
post_process( enum.map{ |value| resolve_value(value) } )
|
1423
|
+
end
|
1312
1424
|
end
|
1313
1425
|
end
|
1314
1426
|
def post_process(results)
|
1427
|
+
#p [:post_process, results]
|
1315
1428
|
self.init.clone.replace(results)
|
1316
1429
|
end
|
1317
1430
|
end
|
@@ -1324,6 +1437,7 @@ class Doodle
|
|
1324
1437
|
# define a collector for appendable collections
|
1325
1438
|
# - collection should provide a :<< method
|
1326
1439
|
def define_collection
|
1440
|
+
# FIXME: don't use eval in 1.9+
|
1327
1441
|
if collector_class.nil?
|
1328
1442
|
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
1329
1443
|
junk = #{name} if !#{name} # force initialization for classes
|
@@ -1361,6 +1475,7 @@ class Doodle
|
|
1361
1475
|
# - collection should provide a :[] method
|
1362
1476
|
def define_collection
|
1363
1477
|
# need to use string eval because passing block
|
1478
|
+
# FIXME: don't use eval in 1.9+
|
1364
1479
|
if collector_class.nil?
|
1365
1480
|
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
1366
1481
|
junk = #{name} if !#{name} # force initialization for classes
|