utilrb 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,11 @@
1
+ require 'utilrb/enumerable/to_s_helper'
1
2
  class Hash
2
3
  # Displays hashes as { a => A, b => B, ... } instead of the standard #join
3
4
  # Unlike #inspect, it calls #to_s on the elements too
4
5
  def to_s
5
- "{" << map { |k, v| "#{k} => #{v}" }.join(", ") << "}"
6
+ EnumerableToString.to_s_helper(self, '{', '}') do |k, v|
7
+ "#{k} => #{v}"
8
+ end
6
9
  end
7
10
  end
8
11
 
@@ -6,8 +6,8 @@ module Kernel
6
6
  # converted to symbols for consistency.
7
7
  #
8
8
  # The following rules apply:
9
- # * if a hash is given, non-nil values are treated as default values.
10
- # * an array is equivalent to a hash where all values are 'nil'
9
+ # * if a hash is given, non-nil values are treated as default values.
10
+ # * an array is equivalent to a hash where all values are 'nil'
11
11
  #
12
12
  # See #validate_options and #filter_and_validate_options
13
13
  #
@@ -54,7 +54,7 @@ module Kernel
54
54
  end
55
55
 
56
56
  # call-seq:
57
- # validate_option(options, name, required, message) { |v| ... }
57
+ # validate_option(options, name, required, message) { |v| ... }
58
58
  # validate_option(options, name, required, message)
59
59
  #
60
60
  # Validates option +name+ in the +options+ hash. If required is true,
@@ -1,9 +1,13 @@
1
1
  class Logger
2
2
  # Forward logger output methods to the logger attribute, so that
3
3
  # we can do
4
+ # module MyModule
5
+ # extend Logger::Forward
6
+ # end
4
7
  # MyModule.debug "debug_info"
5
8
  # instead of
6
9
  # MyModule.logger.debug "debug_info"
10
+ #
7
11
  module Forward
8
12
  [ :debug, :info, :warn, :error, :fatal, :unknown ].each do |level|
9
13
  class_eval <<-EOF
@@ -12,12 +12,12 @@ class Module
12
12
  # singleton.has_ancestor?(MyClass) # => true
13
13
  #
14
14
  def has_ancestor?(klass)
15
- self == klass || self < klass || (is_singleton? && superclass.has_ancestor?(klass))
15
+ self <= klass || (is_singleton? && superclass.has_ancestor?(klass))
16
16
  end
17
17
  end
18
18
  Utilrb.unless_faster do
19
19
  def has_ancestor?(klass) # :nodoc:
20
- self == klass || self < klass || superclass == klass || superclass < klass
20
+ self <= klass || superclass == klass || superclass < klass
21
21
  end
22
22
  end
23
23
  end
@@ -1,14 +1,27 @@
1
1
  require 'utilrb/object/attribute'
2
2
 
3
3
  class Module
4
- # Define 'name' to be a read-only enumerable attribute. The method
5
- # defines a +attr_name+ read-only attribute and an enumerator method
6
- # each_#{name}. +init_block+ is used to initialize the attribute.
4
+ # Support for attributes that are enumerables. This methods defines two
5
+ # methods:
6
+ # obj.attr_name # => enumerable
7
+ # obj.each_name(key = nil) { |value| ... } # => obj
7
8
  #
8
- # The enumerator method accepts a +key+ argument. If the attribute is
9
- # a key => enumerable map, then the +key+ attribute can be used to iterate
10
- #
11
- # +enumerator+ is the name of the enumeration method
9
+ # The first one returns the enumerable object itself. The second one
10
+ # iterates on the values in attr_name. If +key+ is not nil, then #attr_name
11
+ # is supposed to be a hash of enumerables, and +key+ is used to select the
12
+ # enumerable to iterate on.
13
+ #
14
+ # The following calls are equivalent
15
+ # obj.attr_name.each { |value| ... }
16
+ # obj.each_name { |value| ... }
17
+ #
18
+ # And these two are equivalent:
19
+ # obj.attr_name[key].each { |value| ... }
20
+ # obj.each_name(key) { |value| ... }
21
+ #
22
+ # +enumerator+ is the name of the enumeration method we should use.
23
+ # +init_block+, if given, should return the value at which we should
24
+ # initialize #attr_name.
12
25
  def attr_enumerable(name, attr_name = name, enumerator = :each, &init_block)
13
26
  class_eval do
14
27
  attribute(attr_name, &init_block)
@@ -21,6 +34,7 @@ class Module
21
34
  else
22
35
  #{attr_name}.#{enumerator}(&iterator)
23
36
  end
37
+ self
24
38
  end
25
39
  EOF
26
40
  end
@@ -1,8 +1,9 @@
1
1
  class Module
2
- # Defines a +name+ predicate (name should end with '?'), and if writable is true
3
- # a writer method which is +name+ without '?'
2
+ # Defines a +name?+ predicate, and if writable is true a #name= method.
3
+ # Note that +name+ can end with '?', in which case the ending '?' is
4
+ # removed.
4
5
  #
5
- # The predicate reads the instance variable which is +name+ without the '?'
6
+ # The methods use the @name instance variable internally
6
7
  def attr_predicate(name, writable = false)
7
8
  attr_name = name.to_s.gsub(/\?$/, '')
8
9
  attr_reader attr_name
@@ -0,0 +1,26 @@
1
+ class Module
2
+ # Creates <tt>enum_#{name}</tt> method which returs an Enumerator object
3
+ # for the <tt>each_#{enum_name}</tt> method. This enumerator is created
4
+ # once.
5
+ #
6
+ # If +with_arg+ is true, it is supposed that the 'each_' method requires
7
+ # one argument, which is given in argument of the 'enum' method. In that
8
+ # case, an enumerator is created for each argument
9
+ def cached_enum(enum_name, name, with_arg)
10
+ if with_arg
11
+ class_eval <<-EOD
12
+ def enum_#{name}(arg)
13
+ @enum_#{name} ||= Hash.new
14
+ @enum_#{name}[arg] ||= enum_for(:each_#{enum_name}, arg)
15
+ end
16
+ EOD
17
+ else
18
+ class_eval <<-EOD
19
+ def enum_#{name}
20
+ @enum_#{name} ||= enum_for(:each_#{enum_name})
21
+ end
22
+ EOD
23
+ end
24
+ end
25
+ end
26
+
@@ -16,7 +16,7 @@ class Module
16
16
  # define_method_with_block('my_method') do |block, *args|
17
17
  # end
18
18
  #
19
- # +block+ is +nil+ if no block is given on the method call
19
+ # +block+ is +nil+ if no block is given during the method call
20
20
  #
21
21
  def define_method_with_block(name, &mdef)
22
22
  class_eval <<-EOD
@@ -6,11 +6,11 @@ class Module
6
6
  # Includes a module in this one, with support for class extensions
7
7
  #
8
8
  # If a module defines a ClassExtension submodule, then
9
- # * if it is included in a module, the target's ClassExtension
10
- # module includes the source ClassExtension (and if there is no
11
- # ClassExtension in the target, it is created)
12
- # * if it is included in a Class, the ClassExtension module
13
- # extends the class.
9
+ # * if it is included in a module, the target's ClassExtension
10
+ # module includes the source ClassExtension (and if there is no
11
+ # ClassExtension in the target, it is created)
12
+ # * if it is included in a Class, the ClassExtension module
13
+ # extends the class.
14
14
  def include(mod)
15
15
  __instance_include__ mod
16
16
  return unless mod.const_defined?(:ClassExtension)
@@ -15,37 +15,54 @@ class Module
15
15
  class_eval <<-EOF
16
16
  def each_#{name}(key = nil, uniq = true)
17
17
  if key
18
- if #{attribute_name}.has_key?(key)
19
- yield(#{attribute_name}[key])
20
- return self if uniq
18
+ for klass in ancestors
19
+ if klass.instance_variable_defined?(:@#{attribute_name})
20
+ if klass.#{attribute_name}.has_key?(key)
21
+ yield(klass.#{attribute_name}[key])
22
+ return self if uniq
23
+ end
24
+ end
25
+ end
26
+ elsif !uniq
27
+ for klass in ancestors
28
+ if klass.instance_variable_defined?(:@#{attribute_name})
29
+ klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
30
+ end
21
31
  end
22
- elsif uniq
23
- @enum_#{name}_uniq ||= enum_uniq(:each_#{name}, nil, false) { |k, v| k }
24
- @enum_#{name}_uniq.each { |el| yield(el) }
25
- return self
26
32
  else
27
- #{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
28
- end
29
- if superclass = ancestors[1..-1].find { |k| k.respond_to?(:each_#{name}) }
30
- superclass.each_#{name}(key, uniq) { |el| yield(el) }
33
+ seen = Set.new
34
+ for klass in ancestors
35
+ if klass.instance_variable_defined?(:@#{attribute_name})
36
+ klass.#{attribute_name}.#{options[:enum_with]} do |el|
37
+ unless seen.include?(el.first)
38
+ seen << el.first
39
+ yield(el)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
31
45
  end
32
46
  self
33
47
  end
34
48
  def has_#{name}?(key)
35
- return true if #{attribute_name}[key]
36
- if superclass = ancestors[1..-1].find { |k| k.respond_to?(:has_#{name}) }
37
- superclass.has_#{name}(key)
49
+ for klass in ancestors
50
+ if klass.instance_variable_defined?(:@#{attribute_name})
51
+ return true if klass.#{attribute_name}.has_key?(key)
52
+ end
38
53
  end
54
+ false
39
55
  end
40
56
  EOF
41
57
  else
42
58
  class_eval <<-EOF
43
- def each_#{name}(&iterator)
44
- #{attribute_name}.#{options[:enum_with]}(&iterator) if #{attribute_name}
45
- if superclass = ancestors[1..-1].find { |k| k.respond_to?(:each_#{name}) }
46
- superclass.each_#{name} { |el| yield(el) }
59
+ def each_#{name}
60
+ for klass in ancestors
61
+ if klass.instance_variable_defined?(:@#{attribute_name})
62
+ klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
63
+ end
47
64
  end
48
- self
65
+ self
49
66
  end
50
67
  EOF
51
68
  end
@@ -69,38 +86,80 @@ class Module
69
86
  # define a +each_value+ method). +attribute_name+ defines the attribute
70
87
  # name. +init+ is a block called to initialize the attribute.
71
88
  # Valid options in +options+ are:
72
- # map:: the attribute should respond to +[]+. The enumeration method takes two
73
- # arguments, +key+ and +uniq+. If +key+ is given, we iterate on the values
74
- # given by <tt>attribute[key]</tt>. If +uniq+ is true, the enumeration will
75
- # yield at most one value for each +key+ found (so, if both +key+ and +uniq+ are
76
- # given, the enumeration yields at most one value)
89
+ # map::
90
+ # If true, the attribute should respond to +[]+. In that case, the
91
+ # enumeration method is each_value(key = nil, uniq = false) If +key+ is
92
+ # given, we iterate on the values given by <tt>attribute[key]</tt>. If
93
+ # +uniq+ is true, the enumeration will yield at most one value for each
94
+ # +key+ found (so, if both +key+ and +uniq+ are given, the enumeration
95
+ # yields at most one value). See the examples below
96
+ # enum_with:: the enumeration method of the enumerable, if it is not +each+
97
+ #
98
+ # === Example
99
+ # Let's define some classes and look at the ancestor chain
100
+ #
101
+ # class A; end
102
+ # module M; end
103
+ # class B < A; include M end
104
+ # A.ancestors # => [A, Object, Kernel]
105
+ # B.ancestors # => [B, M, A, Object, Kernel]
106
+ #
107
+ # ==== Attributes for which 'map' is not set
108
+ #
109
+ # class A
110
+ # inherited_enumerable("value", "values") do
111
+ # Array.new
112
+ # end
113
+ # end
114
+ # module M
115
+ # inherited_enumerable("mod") do
116
+ # Array.new
117
+ # end
118
+ # end
119
+ #
120
+ # A.values << 1 # => [1]
121
+ # B.values << 2 # => [2]
122
+ # M.mod << 1 # => [1]
123
+ # b = B.new
124
+ # class << b
125
+ # self.values << 3 # => [3]
126
+ # self.mod << 4 # => [4]
127
+ # end
128
+ # M.mod << 2 # => [1, 2]
129
+ #
130
+ # A.enum_for(:each_value).to_a # => [1]
131
+ # B.enum_for(:each_value).to_a # => [2, 1]
132
+ # b.singleton_class.enum_for(:each_value).to_a # => [3, 2, 1]
133
+ # b.singleton_class.enum_for(:each_mod).to_a # => [4, 1, 2]
77
134
  #
78
- # For instance
135
+ # ==== Attributes for which 'map' is set
79
136
  #
80
- # class A
81
- # class_inherited_enumerable("value", "enum") { Array.new }
82
- # end
83
- # module M
84
- # inherited_enumerable("attr") { Array.new }
85
- # end
86
- # class B < A
87
- # include M
88
- # end
89
- # b = B.new
137
+ # class A
138
+ # inherited_enumerable("mapped", "map", :map => true) do
139
+ # Hash.new { |h, k| h[k] = Array.new }
140
+ # end
141
+ # end
142
+ #
143
+ # A.map['name'] = 'A' # => "A"
144
+ # A.map['universe'] = 42
145
+ # B.map['name'] = 'B' # => "B"
146
+ # B.map['half_of_it'] = 21
147
+ #
148
+ # Let's see what happens if we don't specify the key option.
149
+ # A.enum_for(:each_mapped).to_a # => [["name", "A"], ["universe", 42]]
150
+ # If the +uniq+ option is set (the default), we see only B's value for 'name'
151
+ # B.enum_for(:each_mapped).to_a # => [["half_of_it", 21], ["name", "B"], ["universe", 42]]
152
+ # If the +uniq+ option is not set, we see both values for 'name'. Note that
153
+ # since 'map' is a Hash, the order of keys in one class is not guaranteed.
154
+ # Nonetheless, we have the guarantee that values from B appear before
155
+ # those from A
156
+ # B.enum_for(:each_mapped, nil, false).to_a # => [["half_of_it", 21], ["name", "B"], ["name", "A"], ["universe", 42]]
90
157
  #
91
- # A.enum << 1
92
- # B.enum << 2
93
- # M.attr << 1
94
- # class << b
95
- # enum << 3
96
- # attr << 4
97
- # end
98
- # M.attr << 2
99
158
  #
100
- # A.each_enum => 1
101
- # B.each_enum => 2, 1
102
- # b.singleton_class.each_enum => 3, 2, 1
103
- # b.singleton_class.each_attr => 4, 1, 2
159
+ # Now, let's see how 'key' behaves
160
+ # A.enum_for(:each_mapped, 'name').to_a # => ["A"]
161
+ # B.enum_for(:each_mapped, 'name').to_a # => ["B"]
162
+ # B.enum_for(:each_mapped, 'name', false).to_a # => ["B", "A"]
104
163
  #
105
164
  def inherited_enumerable(name, attribute_name = name, options = Hash.new, &init)
106
165
  singleton_class.class_eval { define_inherited_enumerable(name, attribute_name, options, &init) }
@@ -39,49 +39,35 @@ end
39
39
 
40
40
  Utilrb.if_faster do
41
41
  class Object
42
- # :call-seq
43
- # attribute :name => default_value
44
- # attribute(:name) { default_value }
45
- #
46
- # In the first form, defines a read-write attribute
47
- # named 'name' with default_value for default value.
48
- # In the second form, the block is called if the attribute
49
- # is read before it has been ever written, and its return
50
- # value is used as default value.
51
- def attribute(attr_def, &init)
42
+ def attribute(attr_def, &init) # :nodoc:
52
43
  if Hash === attr_def
53
44
  name, defval = attr_def.to_a.flatten
54
45
  else
55
46
  name = attr_def
56
47
  end
57
48
 
58
- if is_singleton? || instance_of?(Module)
59
- class_eval do
60
- attr_writer name
61
- define_method("#{name}_defval") do
62
- defval || (instance_eval(&init) if init)
63
- end
49
+ class_eval do
50
+ attr_writer name
51
+ if !defval && init
52
+ define_method("#{name}_defval", &init)
53
+ else
54
+ define_method("#{name}_defval") { defval }
64
55
  end
56
+ end
65
57
 
66
- class_eval <<-EOD
67
- def #{name}
68
- if defined? @#{name} then @#{name}
69
- else @#{name} = #{name}_defval
70
- end
71
- end
72
- EOD
73
- else
74
- class_eval { attr_writer name }
75
- define_method(name) do
76
- singleton_class.class_eval { attr_reader name }
77
- instance_variable_set("@#{name}", defval || (instance_eval(&init) if init))
58
+ class_eval <<-EOD
59
+ def #{name}
60
+ if defined? @#{name} then @#{name}
61
+ else @#{name} = #{name}_defval
78
62
  end
79
63
  end
64
+ EOD
80
65
  end
81
66
  end
82
67
  end
83
68
 
84
69
  class Object
70
+ # Like #attribute, but on the singleton class of this object
85
71
  def class_attribute(attr_def, &init)
86
72
  singleton_class.class_eval { attribute(attr_def, &init) }
87
73
  end
@@ -7,20 +7,16 @@ end
7
7
 
8
8
  if RUBY_VERSION >= "1.9"
9
9
  class Object
10
- # Returns the singleton class for this object
11
- #
12
- # The first element of #ancestors on the returned singleton class is
13
- # the singleton class itself. A #singleton_instance accessor is also
14
- # defined, which returns the object instance the class is the singleton
15
- # of.
16
- def singleton_class
10
+ def singleton_class # :nodoc:
17
11
  if defined? @singleton_class
18
12
  return @singleton_class
19
13
  else
20
14
  @singleton_class = class << self
21
15
  class << self
22
- alias __ancestors__ ancestors
23
- def ancestors; __ancestors__.unshift(self) end
16
+ alias __ancestors__ ancestors # :nodoc:
17
+ def ancestors # :nodoc:
18
+ __ancestors__.unshift(self)
19
+ end
24
20
  end
25
21
 
26
22
  self
@@ -57,8 +53,10 @@ else
57
53
  "#{@superclass.name}!0x#{@singleton_instance.address.to_s(16)}"
58
54
  end
59
55
 
60
- alias __ancestors__ ancestors
61
- def ancestors; __ancestors__.unshift(self) end
56
+ alias __ancestors__ ancestors # :nodoc:
57
+ def ancestors # :nodoc:
58
+ __ancestors__.unshift(self)
59
+ end
62
60
  end
63
61
  end
64
62