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 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 does not yet
15
- work with Ruby 1.9 or Rubinius.
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) 2008 Sean O'Halpin
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-2008 by Sean O'Halpin
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' # todo[replace this with own (required functions only) version]
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| if i.kind_of? Array then arr.push(*i) else arr.push(i) end }
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
- camel_cased_word.to_s.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
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.keys.each do |key|
121
- normalized_key = key.respond_to?(method) ? key.send(method) : key
122
- v = hash.delete(key)
123
- if recursive
124
- if v.kind_of?(Hash)
125
- v = normalize_keys!(v, recursive, method)
126
- elsif v.kind_of?(Array)
127
- v = v.map{ |x| normalize_keys!(x, recursive, method) }
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
- # fixme: move
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
- # fixme: init deferred blocks are not getting resolved in all cases
680
+ # FIXME: init deferred blocks are not getting resolved in all cases
654
681
  def _getter(name, &block)
655
- #p [:_getter, name]
656
- ivar = "@#{name}"
657
- if instance_variable_defined?(ivar)
658
- #p [:_getter, :instance_variable_defined, name, ivar, instance_variable_get(ivar)]
659
- instance_variable_get(ivar)
660
- else
661
- # handle default
662
- # Note: use :init => value to cover cases where defaults don't work
663
- # (e.g. arrays that disappear when you go out of scope)
664
- att = __doodle__.lookup_attribute(name)
665
- # special case for class/singleton :init
666
- if att && att.optional?
667
- optional_value = att.init_defined? ? att.init : att.default
668
- #p [:optional_value, optional_value]
669
- case optional_value
670
- when DeferredBlock
671
- #p [:deferred_block]
672
- v = instance_eval(&optional_value.block)
673
- when Proc
674
- v = instance_eval(&optional_value)
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
- v = optional_value
677
- end
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
- # fixme: move
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
- # fixme: move
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 fine
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
- if names.empty?
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
- klass = Object
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 && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING)
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
- klass = names.inject(self) {|c, n| c.const_get(n)}
1100
- # todo[check how many times this is being called]
1101
- if name =~ Factory::RX_IDENTIFIER && !klass.respond_to?(name)
1102
- klass.module_eval("def self.#{name}(*args, &block); #{name}.new(*args, &block); end", __FILE__, __LINE__)
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
- post_process( enum.map{ |value| resolve_value(value) } )
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