y_support 2.0.14 → 2.0.15.p1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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