y_support 2.0.14 → 2.0.15.p1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f2417e6453a390dd0001a7add38638b69989f3da
4
- data.tar.gz: 7c430e880d23c375a741572d7758c0dee3edb19b
3
+ metadata.gz: 326d4643c9c678771d09d5c90046d9e1858f887b
4
+ data.tar.gz: 7a87cc4045556e716700a2888573d27bbb99e7d5
5
5
  SHA512:
6
- metadata.gz: 7d4ad637111c552d83e59345ee193b6bb15d9dc1d22e2ef31ca2c38500c70370f05a4a27e64c103b7308a315bc1565c4c6955d38373a37e5945f3729b70ff4d4
7
- data.tar.gz: 6b158e5be9d2652019df69e9b2ee3b3ae0d380283d2d15cfda935c13cf174c6a55370264f9d4eb6006dba3720e8980f20d2670b0f9d7d471258a1cc7470008a1
6
+ metadata.gz: 5049da6a9a4ecde2993d84b81ec38e7ed8a4ca099b49c9c4b807e7926da0fa49f3b993ffe4ccd8e5f8222aeaa5e6ffe31ed4a225bd7701455b886b293de78513
7
+ data.tar.gz: 297c2bde7a494d9c10731f62827fa6f7b068308796314581739ef175521ea0b770025f02ed2a87b7f67a9124e366c976667175d24176a4b37744e681853f63df
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class Array
4
+ # Maps an array to an array of the element names, obtained by applying +#name+
5
+ # method to them. Takes one optional argument, which regulates its behavior
6
+ # regarding unnamed objects. If set to _nil_ (default) unnamed objects will
7
+ # be mapped to _nil_ (default behavior of the +#name+ method). If set to
8
+ # _true_, unnamed objects will be mapped to themselves. If set to _false_,
9
+ # unnamed object will not be mapped at all -- the returned array will contain
10
+ # only the names of the named objects.
11
+ #
12
+ def names option=nil
13
+ return map &:name if option.nil? # unnamed --> nil
14
+ return map { |e| e.name || e } if option == true # unnamed --> instance
15
+ return map( &:name ).compact if option == false # unnamed squeezed out
16
+ fail ArgumentError, "Unknown option: #{option}"
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module NameMagic::ClassMethods
4
+ # Presents the instances registered by the namespace. Takes one optional
5
+ # argument. If set to _false_, the method returns all the instances
6
+ # registered by the namespace. If set to _true_ (default), only returns
7
+ # those instances registered by the namespace, which are of the exact same
8
+ # class as the method's receiver. Example:
9
+ #
10
+ # class Animal; include NameMagic end
11
+ # Cat, Dog = Class.new( Animal ), Class.new( Animal )
12
+ # Spot = Dog.new
13
+ # Livia = Cat.new
14
+ # Animal.instances #=> returns 2 instances
15
+ # Dog.instances #=> returns 1 instance
16
+ # Dog.instances( false ) #=> returns 2 instances of the namespace Animal
17
+ #
18
+ def instances option=false
19
+ const_magic
20
+ return __instances__.keys if option
21
+ __instances__.keys.select { |i| i.kind_of? self }
22
+ end
23
+
24
+ # Presents the instance names. Takes one optional argument, same as
25
+ # #instances method. Unnamed instances are completely disregarded.
26
+ #
27
+ def instance_names option=false
28
+ instances( option ).names( false )
29
+ end
30
+
31
+ # In addition the ability to name objects upon constant assignment, as common
32
+ # with eg. Class instances, NameMagic redefines class method #new so that it
33
+ # swallows the named argument :name (alias :ɴ), and takes care of naming the
34
+ # instance accordingly. Also, :name_avid named argument mey be supplied, which
35
+ # makes the naming avid (able to overwrite the name already in use by
36
+ # another object) if set to _true_.
37
+ #
38
+ def new *args, &block
39
+ oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract hash
40
+ nm = if oo[:name] then oo.delete :name # consume :name if supplied
41
+ elsif oo[:ɴ] then oo.delete :ɴ # consume :ɴ if supplied
42
+ else nil end
43
+ avid = oo[:name_avid] ? oo.delete( :name_avid ) : false # => true/false
44
+ # Avoid overwriting existing names unless avid:
45
+ fail NameError, "#{self} instance #{nm} already exists!" if
46
+ __instances__.keys.include? nm unless avid
47
+ args << oo unless oo.empty? # prepare the arguments
48
+ new_before_name_magic( *args, &block ).tap do |new_inst| # instantiate
49
+ __instances__.update new_inst => nil # Instance is created unnamed...
50
+ namespace.new_instance_closure.tap { |λ|
51
+ λ.( new_inst ) if λ
52
+ if nm then # name has been supplied, we can name the instance:
53
+ avid ? new_inst.name!( nm ) : new_inst.name = nm
54
+ else # name hasn't been supplied, making the instance avid:
55
+ __avid_instances__ << new_inst
56
+ end
57
+ }
58
+ end
59
+ end
60
+
61
+ # Calls #new in _avid_ _mode_ (name_avid: true); see #new method for avid mode
62
+ # explanation.
63
+ #
64
+ def new! *args, &block
65
+ oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract options
66
+ new *args, oo.update( name_avid: true ), &block
67
+ end
68
+ end # module NameMagic::ClassMethods
@@ -0,0 +1,224 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module NameMagic
4
+ module NamespaceMethods
5
+ # Presents class-owned namespace. By default, this is the class itself, but
6
+ # may be overriden to use some other module as a namespace.
7
+ #
8
+ def namespace
9
+ self
10
+ end
11
+
12
+ # Sets the namespace of the class.
13
+ #
14
+ def namespace= modul
15
+ modul.extend ::NameMagic::NamespaceMethods unless modul == self
16
+ tap { define_singleton_method :namespace do modul end }
17
+ end
18
+
19
+ # Makes the class/module its own namespace. This is useful especially to tell
20
+ # the subclasses of a class using NameMagic to maintain their own namespaces.
21
+ #
22
+ def namespace!
23
+ nil.tap { self.namespace = self }
24
+ end
25
+
26
+ # Presents the instances registered by the namespace.
27
+ #
28
+ def instances
29
+ const_magic
30
+ __instances__.keys
31
+ end
32
+
33
+ # Presents the instance names. Takes one optional argument, same as
34
+ # #instances method. Unnamed instances are completely disregarded.
35
+ #
36
+ def instance_names
37
+ instances.names( false )
38
+ end
39
+
40
+ # Presents namespace-owned @instances hash of pairs <code>{ instance =>
41
+ # instance_name }</code>. Unnamed instances have nil value. (Also, this
42
+ # method does not trigger #const_magic.)
43
+ #
44
+ def __instances__
45
+ namespace.instance_variable_get( :@instances ) ||
46
+ namespace.instance_variable_set( :@instances, {} )
47
+ end
48
+
49
+ # Presents namespace-owned @avid_instances array of avid instances. "Avid"
50
+ # means that the instance is able to overwrite a name used by another
51
+ # registered instance. (Also, this method does not trigger const_magic).
52
+ #
53
+ def __avid_instances__
54
+ namespace.instance_variable_get( :@avid_instances ) ||
55
+ namespace.instance_variable_set( :@avid_instances, [] )
56
+ end
57
+
58
+ # Returns the instance identified by the argument. NameError is raised, if
59
+ # the argument does not identify an instance. (It can be an instance name
60
+ # (string/symbol), or an instance itself, in which case, it is just returned
61
+ # back without changes.)
62
+ #
63
+ def instance identifier
64
+ puts "#instance( #{identifier} )" if DEBUG
65
+ # In @instances hash, value 'nil' indicates a nameless instance!
66
+ fail TypeError, "'nil' is not an instance identifier!" if identifier.nil?
67
+ ii = instances
68
+ return identifier if ii.include? identifier # return the instance back
69
+ begin # identifier not a registered instace -- treat it as a name
70
+ ary = [identifier, identifier.to_sym]
71
+ ii.find do |i| ary.include? i.name end
72
+ rescue NoMethodError
73
+ end or raise NameError, "No instance #{identifier} in #{self}."
74
+ end
75
+
76
+ # Searches all the modules in the the object space for constants containing
77
+ # receiver class objects, and names the found instances accordingly. The
78
+ # number of the remaining nameless instances is returned.
79
+ #
80
+ def const_magic
81
+ return 0 if nameless_instances.size == 0
82
+ serve_all_modules
83
+ return nameless_instances.size
84
+ end # def const_magic
85
+
86
+ # Returns those instances, which are nameless (whose name is set to nil).
87
+ #
88
+ def nameless_instances
89
+ __instances__.select { |key, val| val.nil? }.keys
90
+ end
91
+ alias unnamed_instances nameless_instances
92
+ alias anonymous_instances nameless_instances
93
+
94
+ # Clears namespace-owned references to a specified instance. (This is
95
+ # different from "unnaming" an instance by setting <code>inst.name =
96
+ # nil</code>, which makes the instance anonymous, but still registered.)
97
+ #
98
+ def forget( instance_identifier )
99
+ inst = begin
100
+ instance( instance_identifier )
101
+ rescue ArgumentError
102
+ return nil # nothing to forget
103
+ end
104
+ ɴ = inst.nil? ? nil : inst.name
105
+ namespace.send :remove_const, ɴ if ɴ # clear constant assignment
106
+ __instances__.delete( inst ) # remove @instances entry
107
+ __avid_instances__.delete( inst ) # remove if any
108
+ return inst # return the forgotten instance
109
+ end
110
+
111
+ # Clears namespace-owned references to an instance, without performing
112
+ # #const_magic first. The argument should be a registered instance. Returns
113
+ # the instance name, or _false_, if there was no such registered instance.
114
+ #
115
+ def __forget__( instance )
116
+ return false unless __instances__.keys.include? instance
117
+ namespace.send :remove_const, instance.name if instance.name
118
+ __avid_instances__.delete( instance )
119
+ __instances__.delete instance
120
+ end
121
+
122
+ # Clears namespace-owned references to all the anonymous instances.
123
+ #
124
+ def forget_nameless_instances
125
+ nameless_instances.each { |inst, ɴ|
126
+ __instances__.delete inst
127
+ __avid_instances__.delete inst # also from here
128
+ }
129
+ end
130
+ alias forget_unnamed_instances forget_nameless_instances
131
+ alias forget_anonymous_instances forget_nameless_instances
132
+
133
+ # Clears namespace-owned references to all the instances.
134
+ #
135
+ def forget_all_instances
136
+ __instances__.clear # clears @instances
137
+ constants( false ).each { |ß| # clear constants in the namespace
138
+ namespace.send :remove_const, ß if const_get( ß ).is_a? self
139
+ }
140
+ end
141
+
142
+ # Registers a hook to execute whenever name magic creates a new instance of
143
+ # the class including NameMagic. The block should take one argument (the new
144
+ # instance that was created) and is called in #new method right after
145
+ # instantiation, but before naming.
146
+ #
147
+ def new_instance_closure &block
148
+ namespace.new_instance_closure &block unless namespace == self
149
+ @new_instance_closure = block if block
150
+ @new_instance_closure ||= -> instance { instance }
151
+ end
152
+ alias new_instance_hook new_instance_closure
153
+
154
+ # Registers a hook to execute whenever name setting is performed on an
155
+ # instance. The block should take three arguments (instance, name, old_name).
156
+ # The output value of the block is the name to be actually used – the hook
157
+ # thus allows to define transformations on the name when naming. It is the
158
+ # responsibility of the block to output a suitable symbol (capitalized,
159
+ # usable as a constant name etc.)
160
+ #
161
+ def name_set_closure &block
162
+ namespace.name_set_closure &block unless namespace == self
163
+ @name_set_closure = block if block
164
+ @name_set_closure ||= -> name, instance, old_name=nil { name }
165
+ end
166
+ alias name_set_hook name_set_closure
167
+
168
+ # Registers a hook to execute whenever the instance is asked about its
169
+ # name. The name object contained in __instances__[self] is subjected
170
+ # to the name_get_closure before being returned as instance name.
171
+ #
172
+ def name_get_closure &block
173
+ namespace.name_get_closure &block unless namespace == self
174
+ @name_get_closure = block if block
175
+ @name_get_closure ||= -> name { name }
176
+ end
177
+ alias name_get_hook name_get_closure
178
+
179
+ private
180
+
181
+ # Checks all the constants in some module's namespace, recursively.
182
+ #
183
+ def serve_all_modules
184
+ todo = ( nameless_instances + __avid_instances__ ).map( &:object_id ).uniq
185
+ ObjectSpace.each_object Module do |ɱ|
186
+ ɱ.constants( false ).each do |const_ß|
187
+ begin
188
+ ◉ = ɱ.const_get( const_ß ) # insurance against const. loading fails
189
+ rescue LoadError, StandardError; next end
190
+ next unless todo.include? ◉.object_id
191
+ puts "NameMagic: Anonymous object under #{const_ß}!" if DEBUG
192
+ if __avid_instances__.map( &:object_id ).include? ◉.object_id # avid
193
+ puts "NameMagic: It is avid." if DEBUG
194
+ __avid_instances__ # 1. remove from avid list
195
+ .delete_if { |inst| inst.object_id == ◉.object_id }
196
+ ◉.name! const_ß # 2. name rudely
197
+ else puts "NameMagic: It is not avid." if DEBUG # not avid
198
+ ɴ = validate_name( name_set_closure.( const_ß, ◉, nil ) ).to_sym
199
+ puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
200
+ conflicter = begin; namespace.const_get( ɴ ); rescue NameError; end
201
+ if conflicter then
202
+ msg = "Another #{self}-registered instance named '#{ɴ}' exists!"
203
+ fail NameError, msg unless conflicter == ◉
204
+ else # add the instance to the namespace
205
+ __instances__.update( ◉ => ɴ )
206
+ namespace.const_set( ɴ, ◉ )
207
+ end
208
+ end
209
+ todo.delete ◉.object_id # remove the id from todo list
210
+ break if todo.empty? # and break the loop if done
211
+ end # each
212
+ end # each_object Module
213
+ end # def serve_all_modules
214
+
215
+ # Checks whether a name starts with a capital letter.
216
+ #
217
+ def validate_name name
218
+ name.to_s.tap do |ɴ| # check whether the name starts with 'A'..'Z'
219
+ fail NameError, "#{self}-registered name must start with a capital " +
220
+ " letter 'A'..'Z' ('#{ɴ}' given)!" unless ( ?A..?Z ) === ɴ[0]
221
+ end
222
+ end
223
+ end # module NamespaceMethods
224
+ end # module NameMagic
@@ -1,26 +1,61 @@
1
1
  # -*- coding: utf-8 -*-
2
+
2
3
  require 'y_support'
4
+ require_relative 'name_magic/array'
5
+ require_relative 'name_magic/namespace_methods'
6
+ require_relative 'name_magic/class_methods'
3
7
 
4
8
  # This mixin imitates Ruby constant magic and automates the named argument
5
- # :name (alias :ɴ). One thus can write:
9
+ # :name (alias :ɴ). One can write:
6
10
  #
7
- # <tt>class Someclass; include NameMagic end</tt>
8
- # <tt>SomeName = SomeClass.new</tt>
11
+ # require 'y_support/name_magic'
12
+ # class Foo; include NameMagic end
13
+ # Bar = Foo.new
9
14
  #
10
15
  # and the resulting object will know its #name:
11
16
  #
12
- # <tt>SomeName.name = "SomeName"</tt>
17
+ # Bar.name #=> :Bar
18
+ # Foo::Bar #=> <Foo:0x.....>
19
+ #
20
+ # This is done by searching whole Ruby namespace for constants, triggered by the
21
+ # method #const_magic defined in the namespace mixin. (Once the object is named,
22
+ # subsequent constant assignments have no effects.) By default, the namespace
23
+ # is the class, in which NameMagic is included, but it is possible to prescribe
24
+ # another module as a namespace:
25
+ #
26
+ # Quux = Module.new
27
+ # class FooBar
28
+ # include NameMagic
29
+ # self.namespace = Quux
30
+ # end
31
+ # FooBar.new name: "Baz"
32
+ # FooBar::Baz #=> NameError
33
+ # Quux::Baz #=> <FooBar:0x.....>
34
+ #
35
+ # When subclassing the classes with NameMagic included, namespace setting does
36
+ # not change:
13
37
  #
14
- # This is done by searching the whole Ruby namespace for constants, to which
15
- # the object might have been assigned. The search is performed by the method
16
- # #const_magic defined by this mixin. Once the object is found to be assigned
17
- # to a constant, and named accordingly, its subsequent assignments to other
18
- # constants have no additional effect.
38
+ # class Animal; include NameMagic end
39
+ # class Dog < Animal; end
40
+ # class Cat < Animal; end
41
+ # Dog.namespace #=> Animal
42
+ # Cat.namespace #=> Animal
43
+ # Livia = Cat.new
44
+ # Cat.instance_names #=> []
45
+ # Animal.instance_names #=> [:Livia]
19
46
  #
20
- # Alternative way to create a named object is by specifying :name (alias :ɴ)
21
- # named argument:
47
+ # To make the subclasses use each their own namespace, use +#namespace!+ method:
22
48
  #
23
- # <tt>SomeClass.new a, b, ..., name: "SomeName", aa: v1, bb: v2 ...</tt>
49
+ # Dog.namespace!
50
+ #
51
+ # NameMagic also provides an alternative way to create named objects by taking
52
+ # care of :name (alias :ɴ) named argument of the constructor:
53
+ #
54
+ # Dog.new name: "Spot"
55
+ # Dog.new ɴ: :Rover
56
+ # Dog.instance_names #=> [:Spot, :Rover]
57
+ # Animal.instance_names #=> []
58
+ #
24
59
  #
25
60
  # Lastly, a name can be assigned by #name= accssor, as in
26
61
  #
@@ -33,345 +68,85 @@ require 'y_support'
33
68
  module NameMagic
34
69
  DEBUG = false
35
70
 
36
- def self.included ɱ
37
- case ɱ
38
- when Class then # we will decorate its #new method
39
- class << ɱ
40
- alias :original_method_new :new # Make space to decorate #new
71
+ def self.included modul
72
+ if modul.is_a? Class then # decorate #new
73
+ class << modul
74
+ alias :new_before_name_magic :new
41
75
  end
42
- # Attach the decorators etc.
43
- ɱ.extend ::NameMagic::ClassMethods
44
- ɱ.extend ::NameMagic::NamespaceMethods
45
- # Attach namespace methods also to the namespace, if given.
76
+ modul.extend NameMagic::NamespaceMethods
77
+ modul.extend NameMagic::ClassMethods
78
+ # Attach namespace methods also to the namespace, if given
46
79
  begin
47
- if ɱ.namespace == ɱ then
48
- ɱ.define_singleton_method :namespace do ɱ end
80
+ if modul.namespace == modul then
81
+ modul.define_singleton_method :namespace do modul end
49
82
  else
50
- ɱ.namespace.extend ::NameMagic::NamespaceMethods
83
+ modul.namespace.extend NameMagic::NamespaceMethods
51
84
  end
52
85
  rescue NoMethodError
53
86
  end
54
- else # it is a Module; we'll infect it with our #included method
55
- ɱ_included, this_included = ɱ.method( :included ), method( :included )
56
- ɱ.define_singleton_method :included do |ç|
57
- this_included.( ç )
58
- ɱ_included.( ç )
59
- end
87
+ else # it is a Module -- infect it with this #include
88
+ orig, this = modul.method( :included ), method( :included )
89
+ modul.define_singleton_method :included do |m| this.( m ); orig.( m ) end
60
90
  end
61
91
  end # self.included
62
92
 
63
- # Retrieves an instance name (demodulized).
93
+ # The namespace of the instance's class.
94
+ #
95
+ def namespace
96
+ self.class.namespace
97
+ end
98
+
99
+ # Retrieves an instance name.
64
100
  #
65
101
  def name
66
102
  self.class.const_magic
67
- ɴ = self.class.__instances__[ self ]
68
- if ɴ then
69
- name_get_closure = self.class.instance_variable_get :@name_get_closure
70
- name_get_closure ? name_get_closure.( ɴ ) : ɴ
71
- else nil end
103
+ __name__ or ( yield self if block_given? )
72
104
  end
73
105
  alias ɴ name
74
106
 
75
- # Retrieves either an instance name (if present), or an object id.
107
+ # Retrieves the instance name. Does not trigger #const_magic before doing so.
76
108
  #
77
- def name_or_object_id
78
- name || object_id
109
+ def __name__
110
+ ɴ = self.class.__instances__[ self ]
111
+ namespace.name_get_closure.( ɴ ) if ɴ
79
112
  end
80
- alias ɴ_ name_or_object_id
81
113
 
82
114
  # Names an instance, cautiously (ie. no overwriting of existing names).
83
115
  #
84
116
  def name=( ɴ )
85
- puts "NameMagic: Naming with argument #{ɴ}." if DEBUG
86
- # get previous name of this instance, if any
87
- old_ɴ = self.class.__instances__[ self ]
88
- # honor the hook
89
- name_set_closure = self.class.instance_variable_get :@name_set_closure
90
- ɴ = name_set_closure.call( ɴ, self, old_ɴ ) if name_set_closure
91
- ɴ = self.class.send( :validate_capitalization, ɴ ).to_sym
92
- puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
93
- return if old_ɴ == ɴ # already named as required; nothing to do
94
- # otherwise, be cautious about name collision
95
- raise NameError, "Name '#{ɴ}' already exists in " +
96
- "#{self.class} namespace!" if self.class.__instances__.rassoc( ɴ )
97
- # since everything's ok...
98
- self.class.namespace.const_set ɴ, self # write a constant
99
- self.class.__instances__[ self ] = ɴ # write __instances__
100
- self.class.__forget__ old_ɴ # forget the old name of self
117
+ old_ɴ = namespace.__instances__[ self ] # previous name
118
+ if ɴ then puts "NameMagic: Naming with argument #{ɴ}." if DEBUG
119
+ ɴ = namespace.send( :validate_name, # honor the hook
120
+ namespace.name_set_closure.( ɴ, self, old_ɴ ) ).to_sym
121
+ puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
122
+ return if old_ɴ == ɴ # already named as required
123
+ fail NameError, "Name '#{ɴ}' already exists in #{namespace} namespace!" if
124
+ self.class.__instances__.rassoc( ɴ )
125
+ namespace.const_set ɴ, self # write a constant
126
+ namespace.__instances__[ self ] = ɴ # write to @instances
127
+ namespace.__forget__ old_ɴ # forget the old name of self
128
+ else puts "NameMagic: Unnaming #{old_ɴ || self}" if DEBUG
129
+ namespace.__instances__.update( self => nil ) # unname in @instances
130
+ namespace.send :remove_const, old_ɴ if old_ɴ # remove namespace const.
131
+ end
101
132
  end
102
133
 
103
134
  # Names an instance, aggresively (overwrites existing names).
104
135
  #
105
136
  def name!( ɴ )
106
- puts "NameMagic: Rudely naming with argument #{ɴ}." if DEBUG
107
- old_ɴ = self.class.__instances__[ self ] # get instance's old name, if any
108
- # honor the hook
109
- name_set_closure = self.class.instance_variable_get :@name_set_closure
110
- ɴ = name_set_closure.( ɴ, self, old_ɴ ) if name_set_closure
111
- ɴ = self.class.send( :validate_capitalization, ɴ ).to_sym
112
- puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
113
- return false if old_ɴ == ɴ # already named as required; nothing to do
114
- # otherwise, rudely remove the collider, if any
115
- pair = self.class.__instances__.rassoc( ɴ )
116
- self.class.__forget__( pair[0] ) if pair
117
- # and add self to the namespace instead
118
- self.class.namespace.const_set ɴ, self # write a constant
119
- self.class.__instances__[ self ] = ɴ # write to __instances__
120
- self.class.__forget__ old_ɴ # forget the old name of self
121
- return true
122
- end
123
-
124
- module NamespaceMethods
125
- # Presents class-owned @instances hash of { instance => name } pairs.
126
- #
127
- def instances
128
- const_magic
129
- __instances__.keys.select { |i| i.kind_of? self }
130
- end
131
-
132
- # Presents an array of all the instance names (disregarding anonymous
133
- # instances).
134
- #
135
- def instance_names
136
- instances.map( &:name ).compact
137
- end
138
-
139
- # Presents class-owned @instances without const_magic.
140
- #
141
- def __instances__
142
- namespace.instance_variable_get( :@instances ) ||
143
- namespace.instance_variable_set( :@instances, {} )
144
- end
145
-
146
- # Presents class-owned @avid_instances (no const_magic).
147
- #
148
- def __avid_instances__
149
- namespace.instance_variable_get( :@avid_instances ) ||
150
- namespace.instance_variable_set( :@avid_instances, [] )
151
- end
152
-
153
- # Presents class-owned namespace. Normally, this is the class itself,
154
- # but can be overriden so as to define constants holding the instances
155
- # in some other module.
156
- #
157
- def namespace
158
- self
159
- end
160
-
161
- # Makes the class use the namespace supplied as an argument.
162
- #
163
- def namespace= namespc
164
- namespc.extend ::NameMagic::NamespaceMethods unless namespc == self
165
- tap { define_singleton_method :namespace do namespc end }
166
- end
167
-
168
- # Makes the class/module use itself as a namespace. (Useful eg. with
169
- # parametrized subclassing to tell the subclasses to maintain each their
170
- # own namespaces.)
171
- #
172
- def namespace!
173
- self.namespace = self
174
- end
175
-
176
- # Returns the instance identified by the argument. NameError is raised, if
177
- # the argument does not identify an instance. (It can be an instance name
178
- # as string, symbol, or an instance itself, in which case, the instance in
179
- # question is merely returned without changes.)
180
- #
181
- def instance arg
182
- # In @instances hash, name 'nil' means nameless!
183
- puts "NameMagic: #instance called with argument #{arg}." if DEBUG
184
- msg = "'nil' is not a valid argument type for NameMagic#instance method!"
185
- fail TypeError, msg if arg.nil?
186
- # if the argument is an actual instance, just return it
187
- ii = instances
188
- return arg if ii.include? arg
189
- # otherwise, assume arg is a name
190
- begin
191
- ii.find { |i| i.name == arg || i.name == arg.to_sym }
192
- rescue NoMethodError
193
- end or raise NameError, "No instance #{arg} in #{namespace}."
194
- end
195
-
196
- # The method will search all the modules in the the object space for
197
- # receiver class objects assigned to constants, and name these instances
198
- # accordingly. Number of the remaining nameless instances is returned.
199
- #
200
- def const_magic
201
- return 0 if nameless_instances.size == 0
202
- serve_all_modules
203
- return nameless_instances.size
204
- end # def const_magic
205
-
206
- # Returns those instances, which are nameless (@instances hash value is nil).
207
- #
208
- def nameless_instances
209
- __instances__.select { |key, val| val.nil? }.keys
210
- end
211
-
212
- # Clears class-owned references to a specified instance.
213
- #
214
- def forget( which_instance )
215
- inst = begin
216
- instance( which_instance )
217
- rescue ArgumentError
218
- return nil # nothing to forget
219
- end
220
- ɴ = inst.nil? ? nil : inst.name
221
- namespace.send :remove_const, ɴ if ɴ # clear constant assignment
222
- __instances__.delete( inst ) # remove @instances entry
223
- __avid_instances__.delete( inst ) # remove if any
224
- return inst # return forgotten instance
225
- end
226
-
227
- # Clears class-owned references to a specified instance without performing
228
- # #const_magic first. The argument must be an instance of the target class.
229
- #
230
- def __forget__( instance )
231
- name = __instances__.delete instance # remove @instances entry
232
- __avid_instances__.delete( instance ) # remove if any
233
- namespace.send :remove_const, name if name
234
- return instance
235
- end
236
-
237
- # Clears class-owned references anonymous instances.
238
- #
239
- def forget_anonymous_instances
240
- nameless_instances.each { |inst, ɴ|
241
- __instances__.delete inst
242
- __avid_instances__.delete inst
243
- }
244
- end
245
- alias :forget_nameless_instances :forget_anonymous_instances
246
-
247
- # Clears class-owned references to all the instances.
248
- #
249
- def forget_all_instances
250
- __instances__.clear # clears @instances
251
- constants( false ).each { |ß| # clear constants in the namespace
252
- namespace.send :remove_const, ß if const_get( ß ).is_a? self
253
- }
254
- end
255
-
256
- # Registers a hook to execute whenever name magic creates a new instance
257
- # of the class including NameMagic. The block should take one argument
258
- # (the new instance that was created) and is called in #new method right
259
- # after instantiation, but before naming.
260
- #
261
- def new_instance_closure &block; @new_instance_closure = block end
262
-
263
- # Registers a hook to execute whenever name setting is performed on an
264
- # instance. The block should take three arguments (instance, name, old_name).
265
- # The output value of the block is the name to be actually used – the hook
266
- # thus allows to define transformations on the name when naming. It is the
267
- # responsibility of the block to output a suitable symbol (capitalized,
268
- # usable as a constant name etc.)
269
- #
270
- def name_set_closure &block; @name_set_closure = block end
271
-
272
- # Registers a hook to execute whenever the instance is asked about its
273
- # name. The name object contained in __instances__[self] is subjected
274
- # to the name_get_closure before being returned as instance name.
275
- #
276
- def name_get_closure &block; @name_get_closure = block end
277
-
278
- private
279
-
280
- # Checks all the constants in some module's namespace, recursively.
281
- #
282
- def serve_all_modules
283
- todo = ( nameless_instances + __avid_instances__ ).map( &:object_id ).uniq
284
- ObjectSpace.each_object Module do |ɱ| # for all the modules...
285
- # ( puts ɱ if DEBUG ) rescue
286
- ɱ.constants( false ).each do |const_ß| # and all the constants...
287
- begin # insurance against constant dynamic loading fails
288
- ◉ = ɱ.const_get( const_ß )
289
- rescue LoadError, StandardError
290
- next
291
- end
292
- if todo.include? ◉.object_id then # we found a wanted object
293
- puts "NameMagic: Wanted object found under #{const_ß}." if DEBUG
294
- if __avid_instances__.map( &:object_id ).include? ◉.object_id # avid
295
- puts "NameMagic: It is avid." if DEBUG
296
- __avid_instances__ # 1. remove from avid list
297
- .delete_if { |instance| instance.object_id == ◉.object_id }
298
- ◉.name! const_ß # 2. name rudely
299
- else # not avid
300
- puts "NameMagic: It is not avid." if DEBUG
301
- ɴ = if @name_set_closure then # honor name_set_closure
302
- @name_set_closure.( const_ß, ◉, nil )
303
- .tap { |r| puts "The resulting name is #{r}." }
304
- else const_ß end
305
- ɴ = validate_capitalization( ɴ ).to_sym
306
- puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
307
- conflicter = begin # be cautious
308
- namespace.const_get( ɴ )
309
- rescue NameError
310
- end
311
- if conflicter then
312
- puts "NameMagic: Conflicter exists named #{ɴ}." if DEBUG
313
- raise NameError, "Another #{self} named '#{ɴ}' already " +
314
- "exists!" unless conflicter == ◉
315
- else
316
- puts "NameMagic: No conflicter named #{ɴ}, about to use it." if DEBUG
317
- __instances__[ ◉ ] = ɴ # add the instance to the namespace
318
- namespace.const_set ɴ, ◉ # add the instance to the namespace
319
- end
320
- end
321
- todo.delete ◉.object_id # remove the id from todo list
322
- break if todo.empty?
323
- end
324
- end # each
325
- end # each_object Module
326
- end # def serve_all_modules
327
-
328
- # Checks whether a name starts with a capital letter.
329
- #
330
- def validate_capitalization name
331
- ɴ = name.to_s
332
- # check whether the name starts with 'A'..'Z'
333
- raise NameError, "#{self.class} name must start with a capital " +
334
- " letter 'A'..'Z' ('#{ɴ}' was given)!" unless ( ?A..?Z ) === ɴ[0]
335
- return ɴ
137
+ old_ɴ = namespace.__instances__[ self ] # previous name
138
+ if ɴ then puts "NameMagic: Rudely naming with #{ɴ}." if DEBUG
139
+ ɴ = namespace.send( :validate_name, # honor the hook
140
+ namespace.name_set_closure.( ɴ, self, old_ɴ ) ).to_sym
141
+ puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
142
+ return false if old_ɴ == ɴ # already named as required
143
+ pair = namespace.__instances__.rassoc( ɴ )
144
+ namespace.__forget__( pair[0] ) if pair # rudely forget the collider
145
+ namespace.const_set ɴ, self # write a constant
146
+ namespace.__instances__[ self ] = ɴ # write to @instances
147
+ namespace.__forget__ old_ɴ # forget the old name of self
148
+ else
149
+ self.name = nil # unnaming, no collider issues
336
150
  end
337
151
  end
338
-
339
- module ClassMethods
340
- # In addition to 'constant magic' ability (name upon constant assignment),
341
- # NameMagic redefines class method #new so that it eats parameter :name,
342
- # alias :ɴ, and takes care of naming the instance accordingly. Option
343
- # :name_avid can also be supplied (true/false), which makes the naming
344
- # avid if true. (Avid, or aggresive naming means that the instance being
345
- # named overwrites whatever was stored under that name earlier.)
346
- #
347
- def new *args, &block
348
- oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract hash
349
- ɴß = if oo[:name] then oo.delete :name # consume :name if supplied
350
- elsif oo[:ɴ] then oo.delete :ɴ # consume :ɴ if supplied
351
- else nil end
352
- avid = oo[:name_avid] ? oo.delete( :name_avid ) : false # => true/false
353
- # Avoid overwriting existing names unless avid:
354
- raise NameError, "#{self} instance #{ɴß} already exists!" if
355
- __instances__.keys.include? ɴß unless avid
356
- # Instantiate:
357
- args << oo unless oo.empty? # fuse hash
358
- new_inst = original_method_new *args, &block
359
- __instances__.merge! new_inst => nil # Instance is created unnamed
360
- # honor the hook
361
- @new_instance_closure.( new_inst ) if @new_instance_closure
362
- if ɴß then # name was supplied, name the instance
363
- if avid then new_inst.name! ɴß else new_inst.name = ɴß end
364
- else # name wasn't supplied, make the instance avid
365
- __avid_instances__ << new_inst
366
- end
367
- return new_inst # return the new instance
368
- end
369
-
370
- # Calls #new in avid mode (name_avid: true).
371
- #
372
- def new! *args, &block
373
- oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract options
374
- new *args, oo.merge!( name_avid: true ), &block
375
- end
376
- end # module ClassMethods
377
152
  end # module NameMagic
@@ -1,3 +1,3 @@
1
1
  module YSupport
2
- VERSION = "2.0.14"
2
+ VERSION = "2.0.15.p1"
3
3
  end
@@ -14,12 +14,10 @@ describe NameMagic do
14
14
  @reporter = Object.new
15
15
  puts "..."
16
16
  @reporter.singleton_class.class_exec { attr_reader :report, :naming }
17
- @ç.new_instance_closure do |instance|
18
- @reporter.define_singleton_method :report do
19
- "New instance reported"
20
- end
17
+ @ç.namespace.new_instance_closure do |instance|
18
+ @reporter.define_singleton_method :report do "Instance reported" end
21
19
  end
22
- @ç.name_set_closure do |name, instance, old_name|
20
+ @ç.namespace.name_set_closure do |name, instance, old_name|
23
21
  @reporter.define_singleton_method :name_set do
24
22
  "Name of the new instance was #{name}"
25
23
  end
@@ -39,14 +37,14 @@ describe NameMagic do
39
37
  @ç.nameless_instances.must_be_empty
40
38
  @reporter.report.must_equal nil
41
39
  x = @ç.new( name: "Boris" )
42
- @reporter.report.must_equal "New instance reported"
40
+ @reporter.report.must_equal "Instance reported"
43
41
  @reporter.name_set.must_equal "Name of the new instance was Boris"
44
42
  x.name.must_equal :Boris
45
43
  @reporter.name_get.must_equal "Name get closure called on Boris"
46
44
  ufo = @ç.new
47
45
  @ç.nameless_instances.must_equal [ufo]
48
46
  UFO = @ç.new
49
- @reporter.report.must_equal "New instance reported"
47
+ @reporter.report.must_equal "Instance reported"
50
48
  @reporter.name_set.must_equal "Name of the new instance was Boris"
51
49
  UFO.name
52
50
  @reporter.name_set.must_equal "Name of the new instance was UFO"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: y_support
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.14
4
+ version: 2.0.15.p1
5
5
  platform: ruby
6
6
  authors:
7
7
  - boris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-27 00:00:00.000000000 Z
11
+ date: 2013-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -75,6 +75,9 @@ files:
75
75
  - lib/y_support/local_object.rb
76
76
  - lib/y_support/misc.rb
77
77
  - lib/y_support/name_magic.rb
78
+ - lib/y_support/name_magic/array.rb
79
+ - lib/y_support/name_magic/class_methods.rb
80
+ - lib/y_support/name_magic/namespace_methods.rb
78
81
  - lib/y_support/null_object.rb
79
82
  - lib/y_support/respond_to.rb
80
83
  - lib/y_support/stdlib_ext.rb
@@ -123,9 +126,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
126
  version: '0'
124
127
  required_rubygems_version: !ruby/object:Gem::Requirement
125
128
  requirements:
126
- - - '>='
129
+ - - '>'
127
130
  - !ruby/object:Gem::Version
128
- version: '0'
131
+ version: 1.3.1
129
132
  requirements: []
130
133
  rubyforge_project:
131
134
  rubygems_version: 2.0.3