doodle 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|