sub_object 1.0
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 +7 -0
- data/.gitignore +51 -0
- data/LICENSE.txt +20 -0
- data/Makefile +23 -0
- data/README.en.rdoc +287 -0
- data/Rakefile +9 -0
- data/lib/sub_object.rb +222 -0
- data/lib/sub_object/sub_array.rb +26 -0
- data/sub_object.gemspec +52 -0
- data/test/test_sub_object.rb +211 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba4fda603b0841fbf226306a1858819a85a733f8c6863c86bcc7091aa3987dbc
|
4
|
+
data.tar.gz: b61a445e22d45c5a35f224e867f04b74ee6a03300fe8cbc705e4771f9cba3d79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0000c9fe5c0d2910ca8b70d63cdabd0e7cf20461c68df7209d75530475eea2ae5d2ccca9b21cfdc20ee48efabca069331d89d92a519e66533d4349adf23939c7
|
7
|
+
data.tar.gz: 701376479f084de002d3bf88adcbb9806ba2fa34a5ff989f3d5d50fffc153fce99e6b82c40a3afa6a56672c35138f23eaaa991026a2e74caedde76ec9b588608
|
data/.gitignore
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
/vendor/bundle
|
10
|
+
|
11
|
+
# Ignore all logfiles and tempfiles.
|
12
|
+
/log/*
|
13
|
+
/tmp/*
|
14
|
+
!/log/.keep
|
15
|
+
!/tmp/.keep
|
16
|
+
|
17
|
+
.rbenv-version
|
18
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
19
|
+
.rvmrc
|
20
|
+
|
21
|
+
/node_modules
|
22
|
+
/yarn-error.log
|
23
|
+
|
24
|
+
.byebug_history
|
25
|
+
|
26
|
+
*.[oa]
|
27
|
+
*.so
|
28
|
+
*~
|
29
|
+
*.nogem
|
30
|
+
*nogem.*
|
31
|
+
*.bak
|
32
|
+
*.BAK
|
33
|
+
*.backup
|
34
|
+
*.org
|
35
|
+
*.orig
|
36
|
+
*.elc
|
37
|
+
*.pyc
|
38
|
+
\#*\#
|
39
|
+
|
40
|
+
# Elastic Beanstalk Files
|
41
|
+
.elasticbeanstalk/*
|
42
|
+
!.elasticbeanstalk/*.cfg.yml
|
43
|
+
!.elasticbeanstalk/*.global.yml
|
44
|
+
|
45
|
+
# yard
|
46
|
+
*.yardoc
|
47
|
+
|
48
|
+
# Ruby Gem doc
|
49
|
+
*.gem
|
50
|
+
doc/*
|
51
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012-2018 Scott Chacon and others
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Makefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
ALL =
|
2
|
+
|
3
|
+
objs =
|
4
|
+
|
5
|
+
.SUFFIXES: .so .o .c .f
|
6
|
+
|
7
|
+
#.o.so:
|
8
|
+
# ${LD} ${LFLAGS} -o $@ $< ${LINK_LIB}
|
9
|
+
|
10
|
+
all: ${ALL}
|
11
|
+
|
12
|
+
|
13
|
+
.PHONY: clean test doc
|
14
|
+
clean:
|
15
|
+
$(RM) bin/*~
|
16
|
+
|
17
|
+
## You may need RUBYLIB=`pwd`/lib:$RUBYLIB
|
18
|
+
test:
|
19
|
+
rake test
|
20
|
+
|
21
|
+
doc:
|
22
|
+
yard doc; [[ -x ".github" && ( "README.en.rdoc" -nt ".github/README.md" ) ]] && ( ruby -r rdoc -e 'puts RDoc::Markup::ToMarkdown.new.convert ARGF.read' < README.en.rdoc > .github/README.md ; echo ".github/README.md is updated." ) || exit 0
|
23
|
+
|
data/README.en.rdoc
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
|
2
|
+
= SubObject - Parent class for memory-efficient sub-Something
|
3
|
+
|
4
|
+
== Summary
|
5
|
+
|
6
|
+
This class {SubObject} is the parent class for
|
7
|
+
{SubString}[http://rubygems.org/gems/sub_string] and alike.
|
8
|
+
This class expresses Ruby sub-Object (like Sub-String),
|
9
|
+
which are obtained with the +self[i, j]+ method,
|
10
|
+
but taking up negligible memory space, as its instance internally holds
|
11
|
+
the (likely positional, though arbitrary) information +(i, j)+ only. This class provides the base
|
12
|
+
interface so the instance behaves exactly like the original
|
13
|
+
class (String for SubString, for example) as duck-typing, except
|
14
|
+
destructive modification, which is prohibited.
|
15
|
+
|
16
|
+
If the original object (which an instance of this class refers to) is ever destructively modified in a way it changes
|
17
|
+
its hash value, warning will be issued whenever this instance is accessed.
|
18
|
+
|
19
|
+
The entire package of SubObject is found in the
|
20
|
+
{Ruby Gems page}[http://rubygems.org/gems/sub_object]
|
21
|
+
and in
|
22
|
+
{Github}[https://github.com/masasakano/sub_object]
|
23
|
+
|
24
|
+
=== Important note to the developers
|
25
|
+
|
26
|
+
To inherit this class, make sure to include the lines equivalent to
|
27
|
+
the following 2 lines, replacing the method name +:to_str+ (which is for
|
28
|
+
SubString class) to suit your child class.
|
29
|
+
|
30
|
+
TO_SOURCE_METHOD = :to_str
|
31
|
+
alias_method TO_SOURCE_METHOD, :to_source
|
32
|
+
|
33
|
+
== Cencept
|
34
|
+
|
35
|
+
This class takes three parameters in the initialization: *source*, *pos*
|
36
|
+
(position), and *size*. *source* is the original object, and
|
37
|
+
basically the constructed instance of this class holds these three
|
38
|
+
pieces of information only (plus a hash value, strictly speaking).
|
39
|
+
Then, whenever it is referred to, it reconstructs
|
40
|
+
|
41
|
+
source[pos, size]
|
42
|
+
|
43
|
+
and works exactly like +source[pos, size]+ does
|
44
|
+
({duck-typing}[https://en.wikipedia.org/wiki/Duck_typing]),
|
45
|
+
whereas it uses negligible internal memory space on its own. Note the
|
46
|
+
information it needs for the +source+ is basically +source.object_id+
|
47
|
+
or equivalent, which is nothing in terms of memory use.
|
48
|
+
In other words, this class does
|
49
|
+
not create the copy of sub-Object from the original source object, as,
|
50
|
+
for example, +String#[i,j]+ does.
|
51
|
+
|
52
|
+
As an example, the child class {SubString}[http://rubygems.org/gems/sub_string] (provided as a different Gem)
|
53
|
+
works as:
|
54
|
+
|
55
|
+
src = "abcdef"
|
56
|
+
ss = SubString.new(src, -4, 3) # => Similar to "abcdef"[-4,3]
|
57
|
+
print ss # => "cde" (STDOUT)
|
58
|
+
ss+'3p' # => "cde3p"
|
59
|
+
ss.upcase # => "CDE"
|
60
|
+
ss.sub(/^./, 'Q') # => "Qde"
|
61
|
+
ss.is_a?(String) # => true
|
62
|
+
"xy_"+ss # => "xy_cde"
|
63
|
+
"cde" == ss # => true
|
64
|
+
|
65
|
+
Internally the instance holds the source object. Therefore, as long
|
66
|
+
as the instance is alive, the source object is never garbage-collected (GC).
|
67
|
+
|
68
|
+
If the source object has been destructively altered, such as with the
|
69
|
+
destructive method +clear+, the corresponding object of this class
|
70
|
+
is likely not to make sense any more. This class can detect such destructive
|
71
|
+
modification (using the method +Object#hash+) and issues a warning
|
72
|
+
whenever it is accessed after the source object has been destructively
|
73
|
+
altered, unless the appropriate global settings (see the next section)
|
74
|
+
are set to suppress it.
|
75
|
+
|
76
|
+
Similarly, because this class supplies an object with the filter
|
77
|
+
appropriate for the class when it receives any message (i.e., method),
|
78
|
+
it does not make sense to apply a destructive change on the instance of this class.
|
79
|
+
Therefore, whenever a destructive method is applied, this class tries
|
80
|
+
to raise NoMethodError exception. The routine to identify the
|
81
|
+
destructive method relies thoroughly on the method name.
|
82
|
+
The methods ending with "!" are regarded as destructive.
|
83
|
+
Other standard distructive method names are defined in the constant {SubObject::DESTRUCTIVE_METHODS}.
|
84
|
+
Each child class may add entries or modify it.
|
85
|
+
|
86
|
+
Note that if a (likely user-defined) desturctive method passes the check,
|
87
|
+
the result is most likely to be different from intended. It certainly
|
88
|
+
never alters this instance destructively (unless +[]+ method of the
|
89
|
+
source object returns self, which is against the Ruby convention), and
|
90
|
+
the returned value may be not like the expected value.
|
91
|
+
|
92
|
+
=== Potential use
|
93
|
+
|
94
|
+
You can make a subclass of this class, corresponding to any class that
|
95
|
+
has the method of +[i, j]+. For example, *SubArray* class is
|
96
|
+
perfectly possible; indeed it is defined as {SubObject::SubArray}.
|
97
|
+
To be fair, I am afraid *SubArray* class does not have much practical
|
98
|
+
value (apart from the demonstration of the concept!), because the only
|
99
|
+
advantage of this class is to use the minimum memory, whereas the
|
100
|
+
original sub-array as in +Array#[i,j]+ does not take much memory in
|
101
|
+
the first place, given Array is by definition just a container.
|
102
|
+
|
103
|
+
By stark contrast, each sub-String in Ruby default takes up memory
|
104
|
+
according to the length of the sub-String. Consider an example:
|
105
|
+
|
106
|
+
src = "Some very extremely lengthy string.... (snipped)".
|
107
|
+
sub = src[1..-1]
|
108
|
+
|
109
|
+
The variable +sub+ uses up about the same memory of +src+.
|
110
|
+
If a great number of +sub+ is created and held alive, the total memory
|
111
|
+
used by the process can become quicly multifold, even by orders of magnitude.
|
112
|
+
|
113
|
+
This is where this class comes in handy. For example, a parsing
|
114
|
+
program applied to a huge text document with a complex grammar may
|
115
|
+
hold a number of such String variables. By using this class instead
|
116
|
+
of String, it can save some valuable memory.
|
117
|
+
|
118
|
+
That is precisely why
|
119
|
+
{SubString}[http://rubygems.org/gems/sub_string],
|
120
|
+
which is a child class of this class and for String, is registered as an official Gem.
|
121
|
+
(In practice, this class is a generalised version of *SubString*,
|
122
|
+
which Ruby allows as a flexible programming language!)
|
123
|
+
|
124
|
+
== Description
|
125
|
+
|
126
|
+
=== Initialization
|
127
|
+
|
128
|
+
Initialize as follows:
|
129
|
+
|
130
|
+
SubObject.new( source, index1, index2 )
|
131
|
+
|
132
|
+
Usually, +index1+ is the starting index and +index2+ is the size,
|
133
|
+
though how they should be recognized depends on the definition of the method
|
134
|
+
+[i,j]+ of +source+.
|
135
|
+
|
136
|
+
=== Constant
|
137
|
+
|
138
|
+
{SubObject::DESTRUCTIVE_METHODS}:: This public constant is the array holding the list of the names (as String) of the methods that should be recognized as destructive (other than those ending with "!").
|
139
|
+
{SubObject::TO_SOURCE_METHOD}:: This public constant specifies the method that projects to (returns) the original-like instance; e.g., :to_str for String.
|
140
|
+
|
141
|
+
The class variable +TO_SOURCE_METHOD+ is meant to be set by each
|
142
|
+
child class (and child classes only). For example, if it is the subclass for String, it should
|
143
|
+
be +:to_str+. Specifically, the registered method must respond to
|
144
|
+
+source[i,j]+. Nott that in this class (parent class), it is **left unset**, but the (private) method of the
|
145
|
+
same name +to_original_method+ returns +:itself+ instead.
|
146
|
+
|
147
|
+
*WARNING*: Do not set this class variable in this class, as it could result in unexpected behaviours if
|
148
|
+
a child class and the parent class are used simultaneously.
|
149
|
+
|
150
|
+
|
151
|
+
=== Class-global settings and class methods
|
152
|
+
|
153
|
+
When this class is accessed after any alteration of the original
|
154
|
+
source object has been detected, it may issue warning (as the insntace does not make sense any more).
|
155
|
+
The warning is suppressed when the Ruby global variable +$VERBOSE+ is
|
156
|
+
nil (it is false in Ruby default). Or, if the following setting is
|
157
|
+
made (internaly, it sets/reads a class instance variable) and set non-nil,
|
158
|
+
its value precedes +$VERBOSE+:
|
159
|
+
|
160
|
+
SubObject.verbose # => getter
|
161
|
+
SubObject.verbose=true # => setter
|
162
|
+
|
163
|
+
where SubObject should be replaced with the name of your child class that inherits {SubObject}.
|
164
|
+
If this value is true or false, such a warning is issued or suppresed,
|
165
|
+
respectively, regardless of the value of the global variable +$VERBOSE+.
|
166
|
+
|
167
|
+
|
168
|
+
=== Instance methods
|
169
|
+
|
170
|
+
The following is the instance methods of {SubObject} unique to this class.
|
171
|
+
|
172
|
+
+#source()+:: Returns the first argument given in initialization. The returned value is dup-ped and **frozen**.
|
173
|
+
+#pos()+:: Returns the second argument given in initialization (usually meaning the starting index).
|
174
|
+
+#subsize()+:: Returns the third argument given in initialization (usually meaning the size of the sub-"source"). Usually this is equivalent to the method +#size+; this method is introduced in case the class of the +source+ somehow does not have the +#size+ method.
|
175
|
+
+#pos_size()+:: Returns the two-component array of +[pos, subsize]+
|
176
|
+
+#to_source()+:: Returns the instance as close as the original +source+ (the class of it, etc).
|
177
|
+
|
178
|
+
In addition, {#inspect}[SubObject#inspect] is redefined.
|
179
|
+
|
180
|
+
Any public methods but destructive ones that are defined for the +source+ can
|
181
|
+
be applied to the instance of this class.
|
182
|
+
|
183
|
+
== Algorithm
|
184
|
+
|
185
|
+
A child class of this class should redefine the constant
|
186
|
+
+TO_SOURCE_METHOD+ in the child class, as well as setting alias to the
|
187
|
+
appropriate method like,
|
188
|
+
|
189
|
+
TO_SOURCE_METHOD = :to_str
|
190
|
+
alias_method TO_SOURCE_METHOD, :to_source
|
191
|
+
|
192
|
+
(which is an example for SubString class)
|
193
|
+
so the method would project to (return) the corresponding instance as close as the original +source+ (the class of it, etc).
|
194
|
+
For example, it should be +:to_str+ for {SubString}[http://rubygems.org/gems/sub_string] and
|
195
|
+
+:to_ary+ for {SubObject::SubArray}. Providing they are appropriately set, even
|
196
|
+
the equal operator works *regardless of the directions* (as long as the
|
197
|
+
methods in the other classes are designed, considering duck-typing
|
198
|
+
appropriately - which should be fine for the built-in classes).
|
199
|
+
|
200
|
+
Internally, almost any method that the instance receives, except for those specific to this class, is processed in
|
201
|
+
the following order:
|
202
|
+
|
203
|
+
1. {#method_missing}[SubObject#method_missing] (almost any methods except those defined in Object should be processed here)
|
204
|
+
1. +super+ if destuctive (usually NoMethodError, because Object class does not have "destructive" methods, though you may argue +taint+ etc is "destructive")
|
205
|
+
2. Else, +send+ to {#to_source}[SubObject#to_source]
|
206
|
+
2. {#to_source}[SubObject#to_source]
|
207
|
+
1. check whether the original {#source}[SubObject#source] has been altered and issues a warning if the conditions are met.
|
208
|
+
2. +#source+[+#pos+, +#subsize+]
|
209
|
+
3. +send+ to +TO_SOURCE_METHOD+ (+:to_str+ etc; +:itself+ in default)
|
210
|
+
3. Back to the method received
|
211
|
+
1. +send+ the method with the arguments and block to it.
|
212
|
+
|
213
|
+
Note {#respond_to_missing?}[SubObject#respond_to_missing?] is redefined so those instance methods are
|
214
|
+
recognized appropriately by +#respond_to?+ and Ruby built-in
|
215
|
+
+#default?+ sentence.
|
216
|
+
|
217
|
+
(See {this post}[https://stackoverflow.com/questions/44107544/respond-to-versus-defined/58673436#58673436]
|
218
|
+
for how Ruby handles in +#respond_to?+ etc.)
|
219
|
+
|
220
|
+
== Example
|
221
|
+
|
222
|
+
An example of +SubObject+ with a test class +MyC+, which
|
223
|
+
|
224
|
+
1. takes the given single argument in initialization, and keeps it as its instance variable (+@x+),
|
225
|
+
2. returns an own instance having the sum of +@x+, +a+ and +b+ for the method +[a,b]+
|
226
|
+
3. has the method +plus1+, which returns its instance variable plus 1,
|
227
|
+
4. has the method +dest!+
|
228
|
+
|
229
|
+
class MyC
|
230
|
+
def initialize(a); @x = a; end
|
231
|
+
def [](a, b); MyC.new(a+b); end
|
232
|
+
def plus1; @x+1; end
|
233
|
+
def to_s; @x.to_s; end
|
234
|
+
def dest!; end
|
235
|
+
end
|
236
|
+
|
237
|
+
# How MyC behaves.
|
238
|
+
myo = MyC.new(8)
|
239
|
+
myo.is_a?(MyC) # => true
|
240
|
+
myo.to_s # => "1"
|
241
|
+
myo.plus1 # => 2 # (= 8+1) (self is unmodified)
|
242
|
+
|
243
|
+
# How "Sub"-MyC behaves.
|
244
|
+
obj = SubObject.new(myo, 2, 3)
|
245
|
+
obj.respond_to?(:plus1) # => true
|
246
|
+
obj.to_s # => "6" # (= 1+(2+3))
|
247
|
+
obj.plus1 # => 7 # (= 6+1)
|
248
|
+
obj.dest! # => raise NoMethodError (destructive method)
|
249
|
+
|
250
|
+
(SubObject === obj) # => true
|
251
|
+
obj.instance_of?(SubObject) # => true
|
252
|
+
obj.is_a?( SubObject) # => false
|
253
|
+
(MyC === obj) # => false
|
254
|
+
obj.instance_of?(MyC) # => false
|
255
|
+
obj.is_a?( MyC) # => true
|
256
|
+
|
257
|
+
== Install
|
258
|
+
|
259
|
+
This script requires {Ruby}[http://www.ruby-lang.org] Version 2.0
|
260
|
+
or above.
|
261
|
+
|
262
|
+
You can install it from the usual Ruby gem command.
|
263
|
+
Or, alternatively, download it and put the library file in one of your Ruby library search paths.
|
264
|
+
|
265
|
+
== Developer's note
|
266
|
+
|
267
|
+
The master of this README file is found in
|
268
|
+
{RubyGems/sub_object}[https://rubygems.org/gems/sub_object]
|
269
|
+
|
270
|
+
=== Tests
|
271
|
+
|
272
|
+
Ruby codes under the directory <tt>test/</tt> are the test scripts.
|
273
|
+
You can run them from the top directory as <tt>ruby test/test_****.rb</tt>
|
274
|
+
or simply run <tt>make test</tt>.
|
275
|
+
|
276
|
+
|
277
|
+
== Known bugs and Todo items
|
278
|
+
|
279
|
+
* This class ignores any optional (keyword) parameters for the methods. It is due to the fact Ruby {BasicObject#method_missing}[https://ruby-doc.org/core-2.6.5/BasicObject.html#method-i-method_missing] does not take them into account as of Ruby-2.6.5. It may change in future versions of Ruby.
|
280
|
+
|
281
|
+
|
282
|
+
== Copyright
|
283
|
+
|
284
|
+
Author:: Masa Sakano < info a_t wisebabel dot com >
|
285
|
+
Versions:: The versions of this package follow Semantic Versioning (2.0.0) http://semver.org/
|
286
|
+
License:: MIT
|
287
|
+
|
data/Rakefile
ADDED
data/lib/sub_object.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Parent class for SubString and SubObject::SubArray or similar.
|
4
|
+
#
|
5
|
+
# == Summary
|
6
|
+
#
|
7
|
+
# This class SubObject is the parent class for
|
8
|
+
# {SubString}[http://rubygems.org/gems/sub_string] and alike.
|
9
|
+
# This class expresses Ruby sub-Object (like Sub-String),
|
10
|
+
# which are obtained with the +self[i, j]+ method,
|
11
|
+
# but taking up negligible memory space, as its instance internally holds
|
12
|
+
# the (likely positional, though arbitrary) information +(i, j)+ only. It provides the basic
|
13
|
+
# interface so the instance behaves exactly like the original
|
14
|
+
# class (String for SubString, for example) as duck-typing, except
|
15
|
+
# destructive modification, which is prohibited.
|
16
|
+
#
|
17
|
+
# If the original object is destructively modified in a way it changes
|
18
|
+
# its hash value, warning is issued whenever this instance is accessed.
|
19
|
+
#
|
20
|
+
# == Important note to the developers
|
21
|
+
#
|
22
|
+
# To inherit this class, make sure to include the lines equivalent to
|
23
|
+
# the following 2 lines, replacing the method name +:to_str+ (which is for
|
24
|
+
# SubString class) to suit your child class.
|
25
|
+
#
|
26
|
+
# TO_SOURCE_METHOD = :to_str
|
27
|
+
# alias_method TO_SOURCE_METHOD, :to_source
|
28
|
+
#
|
29
|
+
# The full reference is found in the top page
|
30
|
+
# {SubObject}[http://rubygems.org/gems/sub_object]
|
31
|
+
# and in {Github}[https://github.com/masasakano/sub_object]
|
32
|
+
#
|
33
|
+
# @author Masa Sakano (Wise Babel Ltd)
|
34
|
+
#
|
35
|
+
class SubObject
|
36
|
+
# The verbosity flag specific to this class, which has a priority
|
37
|
+
# over the global $VERBOSE if set non-nil. If this is true or false,
|
38
|
+
# the warning is always issued or suppressed, respectively, when
|
39
|
+
# an instance is accessed after the source object is destructively modified.
|
40
|
+
# NOTE this class instance variable is specific to this class and
|
41
|
+
# is NOT shared with or inherited to its children!
|
42
|
+
@verbosity = nil
|
43
|
+
|
44
|
+
# Symbol of the method that projects to (returns) the original-like instance;
|
45
|
+
# e.g., :to_str for String. The value should be overwritten in the child class of {SubObject}.
|
46
|
+
TO_SOURCE_METHOD = :itself
|
47
|
+
|
48
|
+
# Symbol of the method to get the original-like instance; e.g., :to_str for String.
|
49
|
+
# The value should be set in the child class, along with the crucial alias, e.g.,
|
50
|
+
#
|
51
|
+
# TO_SOURCE_METHOD = :to_str
|
52
|
+
# alias_method TO_SOURCE_METHOD, :to_source
|
53
|
+
#
|
54
|
+
# @return [Symbol] Method name like :to_str
|
55
|
+
def to_source_method
|
56
|
+
self.class::TO_SOURCE_METHOD
|
57
|
+
end
|
58
|
+
private :to_source_method
|
59
|
+
|
60
|
+
# Getter of the class instance variable @verbosity
|
61
|
+
def self.verbose
|
62
|
+
(defined? @verbosity) ? @verbosity : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Setter of the class instance variable @@verbosity
|
66
|
+
def self.verbose=(obj)
|
67
|
+
@verbosity=obj
|
68
|
+
end
|
69
|
+
|
70
|
+
# Warning/Error messages.
|
71
|
+
ERR_MSGS = {
|
72
|
+
no_method_error: 'source does not accept #[] method',
|
73
|
+
argument_error: 'source does not accept #[i, j]-type method',
|
74
|
+
type_error: 'wrong type for (pos, size)=(%s, %s)', # Specify (pos,size) as the argument.
|
75
|
+
}
|
76
|
+
private_constant :ERR_MSGS # since Ruby 1.9.3
|
77
|
+
|
78
|
+
# List of the names (String) of destructive methods (other than those ending with "!").
|
79
|
+
DESTRUCTIVE_METHODS = %w( []= << clear concat force_encoding insert prepend replace )
|
80
|
+
|
81
|
+
# Starting (character) position
|
82
|
+
attr_reader :pos
|
83
|
+
|
84
|
+
# Size of SubObject. Identical with #size
|
85
|
+
# attr_reader :isize
|
86
|
+
|
87
|
+
# Returns a new instance of SubObject equivalent to source[ pos, size ]
|
88
|
+
#
|
89
|
+
# @param source [String]
|
90
|
+
# @param pos [Integer]
|
91
|
+
# @param size [Integer]
|
92
|
+
def initialize(source, pos, size)
|
93
|
+
@source, @pos, @isize = source, pos, size
|
94
|
+
|
95
|
+
# Sanity check
|
96
|
+
begin
|
97
|
+
_ = @source[@pos, @isize]
|
98
|
+
rescue NoMethodError
|
99
|
+
raise TypeError, ERR_MSGS[:no_method_error]
|
100
|
+
rescue ArgumentError
|
101
|
+
raise TypeError, ERR_MSGS[:argument_error]
|
102
|
+
rescue TypeError
|
103
|
+
raise TypeError, ERR_MSGS[:type_error]%[pos.inspect, size.inspect]
|
104
|
+
end
|
105
|
+
|
106
|
+
if !source.respond_to? self.class::TO_SOURCE_METHOD
|
107
|
+
raise TypeError, "Wrong source class #{source.class.name} for this class #{self.class.name}"
|
108
|
+
end
|
109
|
+
|
110
|
+
# Hash value retained to check its potential destructive change
|
111
|
+
@hash = @source.hash
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [Aray]
|
115
|
+
def pos_size
|
116
|
+
[@pos, @isize]
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return usually the size (Integer)
|
120
|
+
def subsize
|
121
|
+
@isize
|
122
|
+
end
|
123
|
+
|
124
|
+
# Frozen String returned.
|
125
|
+
def source
|
126
|
+
warn_hash
|
127
|
+
src = @source.dup
|
128
|
+
src.freeze
|
129
|
+
src
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the original representation of the instance as in the source
|
133
|
+
#
|
134
|
+
# Each child class should set the constant TO_SOURCE_METHOD
|
135
|
+
# appropriately and should alias this method to the method registered
|
136
|
+
# to TO_SOURCE_METHOD . For example, for SubString,
|
137
|
+
#
|
138
|
+
# TO_SOURCE_METHOD = :to_str
|
139
|
+
# alias_method TO_SOURCE_METHOD, :to_source
|
140
|
+
#
|
141
|
+
# Warning: DO NOT OVERWRITE THIS METHOD.
|
142
|
+
#
|
143
|
+
# @return [Object]
|
144
|
+
def to_source
|
145
|
+
warn_hash
|
146
|
+
@source[@pos, @isize].send(to_source_method)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Method aliases to back up the Object default originals
|
150
|
+
%i(== === is_a? :kind_of? =~ <=> to_s).each do |ec|
|
151
|
+
begin
|
152
|
+
alias_method :to_s_before_sub_object, ec if !method_defined? :to_s_before_sub_object
|
153
|
+
rescue NameError
|
154
|
+
warn "The method {#ec.inspect} is disabled in Object(!!), hence for #{name}" if !$VERBOSE.nil?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Redefining some methods in Object
|
159
|
+
#
|
160
|
+
# Statements repeated, because Module#define_method uses instance_eval
|
161
|
+
# at run-time, which is inefficient.
|
162
|
+
|
163
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
164
|
+
def ==( *rest); to_source.send(__method__, *rest); end
|
165
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
166
|
+
def ===( *rest); to_source.send(__method__, *rest); end
|
167
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
168
|
+
def is_a?( *rest); to_source.send(__method__, *rest); end
|
169
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
170
|
+
def kind_of?(*rest); to_source.send(__method__, *rest); end
|
171
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
172
|
+
def =~( *rest); to_source.send(__method__, *rest); end
|
173
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
174
|
+
def <=>( *rest); to_source.send(__method__, *rest); end
|
175
|
+
# Redefining a method in Object to evaluate via {#to_source}
|
176
|
+
def to_s( *rest); to_source.send(__method__, *rest); end
|
177
|
+
|
178
|
+
alias_method :inspect_before_sub_object, :inspect
|
179
|
+
|
180
|
+
# @return [String]
|
181
|
+
def inspect
|
182
|
+
warn_hash
|
183
|
+
sprintf('%s[%d,%d]%s', self.class.name.split(/::/)[-1], @pos, @isize, @source[@pos, @isize].inspect)
|
184
|
+
end
|
185
|
+
|
186
|
+
# method_missing for any but destructive methods
|
187
|
+
#
|
188
|
+
# @return [Object]
|
189
|
+
# @see #respond_to_missing?
|
190
|
+
def method_missing(method_name, *rest, &block)
|
191
|
+
destructive_method?(method_name) ? super : to_source.send(method_name, *rest, &block)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Obligatory redefinition, following redefined {#method_missing}
|
195
|
+
def respond_to_missing?(method_name, *rest)
|
196
|
+
destructive_method?(method_name) ? super : to_source.send(:respond_to?, method_name, *rest)
|
197
|
+
end
|
198
|
+
|
199
|
+
##################
|
200
|
+
private
|
201
|
+
##################
|
202
|
+
|
203
|
+
def destructive_method?(method_name)
|
204
|
+
met = method_name.to_s
|
205
|
+
(/!$/ =~ met) || DESTRUCTIVE_METHODS.include?(met)
|
206
|
+
end
|
207
|
+
private :destructive_method?
|
208
|
+
|
209
|
+
def warn_hash
|
210
|
+
klass = self.class
|
211
|
+
verbosity_loc = (klass.instance_variable_defined?(:@verbosity) ? klass.instance_variable_get(:@verbosity) : nil)
|
212
|
+
if ((verbosity_loc.nil? && !$VERBOSE.nil?) || verbosity_loc) && @hash != @source.hash
|
213
|
+
str = @source[@pos, @isize].inspect
|
214
|
+
# Suppresses the length only for warning (not the standard inspect), which would be printed repeatedly.
|
215
|
+
str = str[0,60]+'..."' if str.size > 64
|
216
|
+
# warn() would not run if $VERBOSE.nil? (in this case it should run if @verbosity is true).
|
217
|
+
$stderr.printf("WARNING: source string has destructively changed: %s[%d,%d]%s\n", self.class.name, @pos, @isize, str)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
private :warn_hash
|
221
|
+
end
|
222
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'sub_object' # Gem: {SubObject}[http://rubygems.org/gems/sub_object]
|
4
|
+
|
5
|
+
class SubObject
|
6
|
+
|
7
|
+
# Child class of SubObject for Array: SubObject::SubArray
|
8
|
+
#
|
9
|
+
# See for detail the full reference in the top page
|
10
|
+
# {SubObject}[http://rubygems.org/gems/sub_object]
|
11
|
+
# and in {Github}[https://github.com/masasakano/sub_object]
|
12
|
+
#
|
13
|
+
# @author Masa Sakano (Wise Babel Ltd)
|
14
|
+
#
|
15
|
+
class SubArray < SubObject
|
16
|
+
# Symbol of the method that projects to (returns) the original-like instance;
|
17
|
+
# e.g., :to_str for String. The value should be overwritten in the child class of {SubObject}.
|
18
|
+
TO_SOURCE_METHOD = :to_ary
|
19
|
+
alias_method TO_SOURCE_METHOD, :to_source
|
20
|
+
|
21
|
+
ar = %w( append delete delete_at delete_if fill keep_if push pop shift unshift )
|
22
|
+
# @note Add to the list of destructive method: append delete delete_at delete_if fill keep_if push pop shift unshift
|
23
|
+
DESTRUCTIVE_METHODS.concat ar
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/sub_object.gemspec
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{sub_object}.sub(/.*/){|c| (c == File.basename(Dir.pwd)) ? c : raise("ERROR: s.name=(#{c}) in gemspec seems wrong!")}
|
8
|
+
s.version = "1.0"
|
9
|
+
# s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
# s.bindir = 'bin'
|
11
|
+
# %w(sub_object).each do |f|
|
12
|
+
# path = s.bindir+'/'+f
|
13
|
+
# File.executable?(path) ? s.executables << f : raise("ERROR: Executable (#{path}) is not executable!")
|
14
|
+
# end
|
15
|
+
s.authors = ["Masa Sakano"]
|
16
|
+
s.date = %q{2019-11-05}.sub(/.*/){|c| (Date.parse(c) == Date.today) ? c : raise("ERROR: s.date=(#{c}) is not today!")}
|
17
|
+
s.summary = %q{Parent class for memory-efficient sub-Something}
|
18
|
+
s.description = %q{Class SubObject that is the parent class for Ruby SubString and SubArray classes and alike, providing the base interface. This and child classes use negligible memory space, as their instance holds the positional information only. It behaves exactly like the source object (duck-typing), except destructive modification is prohibited. If the original source object is destructively altered, the corresponding instance can detect it and issue warning.}
|
19
|
+
# s.email = %q{abc@example.com}
|
20
|
+
s.extra_rdoc_files = [
|
21
|
+
#"LICENSE.txt",
|
22
|
+
"README.en.rdoc",
|
23
|
+
]
|
24
|
+
s.license = 'MIT'
|
25
|
+
s.files = FileList['.gitignore','lib/**/*.rb','[A-Z]*','test/**/*.rb', '*.gemspec'].to_a.delete_if{ |f|
|
26
|
+
ret = false
|
27
|
+
arignore = IO.readlines('.gitignore')
|
28
|
+
arignore.map{|i| i.chomp}.each do |suffix|
|
29
|
+
if File.fnmatch(suffix, File.basename(f))
|
30
|
+
ret = true
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
ret
|
35
|
+
}
|
36
|
+
s.files.reject! { |fn| File.symlink? fn }
|
37
|
+
|
38
|
+
# s.add_runtime_dependency 'rails'
|
39
|
+
# s.add_development_dependency "bourne", [">= 0"]
|
40
|
+
s.homepage = %q{https://www.wisebabel.com}
|
41
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
42
|
+
|
43
|
+
# s.require_paths = ["lib"] # Default "lib"
|
44
|
+
s.required_ruby_version = '>= 2.0' # respond_to_missing?
|
45
|
+
s.test_files = Dir['test/**/*.rb']
|
46
|
+
s.test_files.reject! { |fn| File.symlink? fn }
|
47
|
+
# s.requirements << 'libmagick, v6.0' # Simply, info to users.
|
48
|
+
# s.rubygems_version = %q{1.3.5} # This is always set automatically!!
|
49
|
+
|
50
|
+
s.metadata["yard.run"] = "yri" # use "yard" to build full HTML docs.
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# @author Masa Sakano (Wise Babel Ltd)
|
4
|
+
|
5
|
+
#require 'open3'
|
6
|
+
require 'sub_object'
|
7
|
+
require 'sub_object/sub_array'
|
8
|
+
|
9
|
+
$stdout.sync=true
|
10
|
+
$stderr.sync=true
|
11
|
+
# print '$LOAD_PATH=';p $LOAD_PATH
|
12
|
+
|
13
|
+
#################################################
|
14
|
+
# Unit Test
|
15
|
+
#################################################
|
16
|
+
|
17
|
+
gem "minitest"
|
18
|
+
# require 'minitest/unit'
|
19
|
+
require 'minitest/autorun'
|
20
|
+
|
21
|
+
class TestUnitSubObject < MiniTest::Test
|
22
|
+
T = true
|
23
|
+
F = false
|
24
|
+
SCFNAME = File.basename(__FILE__)
|
25
|
+
EXE = "%s/../bin/%s" % [File.dirname(__FILE__), File.basename(__FILE__).sub(/^test_(.+)\.rb/, '\1')]
|
26
|
+
|
27
|
+
class MyA
|
28
|
+
end
|
29
|
+
|
30
|
+
class MyB
|
31
|
+
def [](a)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class MyC
|
36
|
+
attr_reader :x
|
37
|
+
def initialize(a)
|
38
|
+
@x = a
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](a, b)
|
42
|
+
raise TypeError if a.respond_to? :to_hash
|
43
|
+
MyC.new(@x+a+b)
|
44
|
+
end
|
45
|
+
|
46
|
+
def plus1
|
47
|
+
@x+1
|
48
|
+
end
|
49
|
+
def to_s
|
50
|
+
@x.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def dest!
|
54
|
+
end
|
55
|
+
|
56
|
+
# Without this you could not compare (once it is frozen)
|
57
|
+
def ==(other)
|
58
|
+
@x == other.x
|
59
|
+
end
|
60
|
+
|
61
|
+
# Test of hash values
|
62
|
+
def hash
|
63
|
+
@x
|
64
|
+
end
|
65
|
+
def modify_self!
|
66
|
+
@x = @x * 9
|
67
|
+
end
|
68
|
+
def reverse_self!
|
69
|
+
@x = @x / 9
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def setup
|
74
|
+
end
|
75
|
+
|
76
|
+
def teardown
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_sub_object01
|
80
|
+
err = assert_raises(TypeError){ SubObject.new MyA.new, 2, 3 }
|
81
|
+
assert_match( /\] method/, err.message )
|
82
|
+
err = assert_raises(TypeError){ SubObject.new MyB.new, 2, 3 }
|
83
|
+
assert_match( /type method/, err.message )
|
84
|
+
err = assert_raises(TypeError){ SubObject.new MyC.new(8), 2, Hash.new[3] }
|
85
|
+
assert_match( /type for/, err.message )
|
86
|
+
|
87
|
+
myo = MyC.new(1)
|
88
|
+
assert myo.is_a?(MyC)
|
89
|
+
assert_equal "1", myo.to_s
|
90
|
+
assert_equal 2, myo.plus1
|
91
|
+
assert_nil myo.dest!
|
92
|
+
assert_equal 1, myo.hash
|
93
|
+
|
94
|
+
obj = SubObject.new myo, 2, 3
|
95
|
+
assert_equal myo, obj.source
|
96
|
+
refute_equal myo.object_id, obj.source.object_id # b/c the latter is frozen
|
97
|
+
assert_equal myo.object_id, obj.instance_variable_get(:@source).object_id
|
98
|
+
assert_equal 2, obj.pos
|
99
|
+
assert_equal 3, obj.subsize
|
100
|
+
assert_equal [2, 3], obj.pos_size
|
101
|
+
assert_equal MyC.new(myo.x+2+3), obj.to_source
|
102
|
+
assert_equal "SubObject", obj.class.name
|
103
|
+
assert_equal "6", obj.to_s
|
104
|
+
assert myo.respond_to?(:to_s)
|
105
|
+
assert obj.respond_to?(:to_s)
|
106
|
+
assert myo.respond_to?(:plus1)
|
107
|
+
assert obj.respond_to?(:plus1)
|
108
|
+
refute myo.respond_to?(:naiyo)
|
109
|
+
refute obj.respond_to?(:naiyo)
|
110
|
+
assert_equal 7, obj.plus1
|
111
|
+
assert (SubObject === obj)
|
112
|
+
assert_instance_of(SubObject, obj)
|
113
|
+
refute obj.kind_of?(SubObject)
|
114
|
+
refute obj.is_a?( SubObject)
|
115
|
+
refute (MyC === obj)
|
116
|
+
refute_instance_of(MyC, obj)
|
117
|
+
assert obj.kind_of?(MyC)
|
118
|
+
assert obj.is_a?( MyC)
|
119
|
+
err = assert_raises(NoMethodError){ obj.to_str }
|
120
|
+
err = assert_raises(NoMethodError){ obj.dest! }
|
121
|
+
|
122
|
+
assert_equal 1, myo.hash
|
123
|
+
assert_equal 1, obj.instance_variable_get(:@hash)
|
124
|
+
myo.modify_self!
|
125
|
+
assert_equal 1, obj.instance_variable_get(:@hash)
|
126
|
+
assert_equal 9, myo.hash
|
127
|
+
assert_equal myo.object_id, obj.instance_variable_get(:@source).object_id
|
128
|
+
assert_equal myo.hash, obj.instance_variable_get(:@source).hash
|
129
|
+
assert_equal 9, obj.instance_variable_get(:@source).hash
|
130
|
+
assert_output('', /destructively/){ obj.source }
|
131
|
+
assert_output('', /destructively/){ obj.plus1 }
|
132
|
+
assert_output('', /destructively/){ obj.to_s }
|
133
|
+
## The following is true and it is as expected,
|
134
|
+
# However, you do not care what it returns once the source
|
135
|
+
# has been destructively modified... Hence taken out of this test.
|
136
|
+
# assert_equal "14", obj.to_s
|
137
|
+
|
138
|
+
mutex = Mutex.new
|
139
|
+
exclu = Thread.new {
|
140
|
+
mutex.synchronize {
|
141
|
+
org_verbose = $VERBOSE
|
142
|
+
assert_output('', ''){ _ = SubObject.verbose }
|
143
|
+
begin
|
144
|
+
$VERBOSE = true
|
145
|
+
assert_nil SubObject.verbose
|
146
|
+
SubObject.verbose=nil;
|
147
|
+
assert_nil SubObject.verbose
|
148
|
+
assert_output('', /destructively/){ obj.source }
|
149
|
+
$VERBOSE = false
|
150
|
+
assert_output('', /destructively/){ obj.source }
|
151
|
+
$VERBOSE = nil
|
152
|
+
assert_output('', ''){ obj.source }
|
153
|
+
|
154
|
+
SubObject.verbose = true
|
155
|
+
assert_equal true, SubObject.verbose
|
156
|
+
assert_equal true, SubObject.instance_variable_get(:@verbosity)
|
157
|
+
assert_output('', /destructively/){ obj.source }
|
158
|
+
SubObject.verbose = false
|
159
|
+
assert_equal false, SubObject.verbose
|
160
|
+
assert_output('', ''){ obj.source }
|
161
|
+
$VERBOSE = true
|
162
|
+
assert_output('', ''){ obj.source }
|
163
|
+
SubObject.verbose=nil;
|
164
|
+
assert_output('', /destructively/){ obj.source }
|
165
|
+
|
166
|
+
# Original String recovered, hence its hash value.
|
167
|
+
myo.reverse_self!
|
168
|
+
assert_output('', ''){ obj.source }
|
169
|
+
ensure
|
170
|
+
$VERBOSE = org_verbose
|
171
|
+
SubObject.verbose=nil;
|
172
|
+
end
|
173
|
+
}
|
174
|
+
}
|
175
|
+
exclu.join
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_sub_array01
|
179
|
+
assert_raises(TypeError){ SubObject::SubArray.new "bad", -3, 2 }
|
180
|
+
|
181
|
+
ary = [2,4,6,8,10]
|
182
|
+
obj = SubObject::SubArray.new ary, -3, 2
|
183
|
+
assert_equal( -3, obj.pos)
|
184
|
+
assert_equal 2, obj.subsize
|
185
|
+
assert_equal [-3, 2], obj.pos_size
|
186
|
+
assert_equal ary[-3, 2], obj.to_source
|
187
|
+
assert_equal :to_ary, SubObject::SubArray::TO_SOURCE_METHOD
|
188
|
+
assert_equal :itself, SubObject::TO_SOURCE_METHOD
|
189
|
+
assert_raises(TypeError){ SubObject::SubArray.new ary, -3, :a }
|
190
|
+
assert (SubObject === obj)
|
191
|
+
assert (SubObject::SubArray === obj)
|
192
|
+
assert obj.instance_of?(SubObject::SubArray)
|
193
|
+
refute obj.instance_of?(SubObject)
|
194
|
+
refute obj.kind_of?(SubObject)
|
195
|
+
refute obj.is_a?( SubObject)
|
196
|
+
refute obj.kind_of?(SubObject::SubArray)
|
197
|
+
refute obj.is_a?( SubObject::SubArray)
|
198
|
+
assert_equal [6,8], obj.to_ary
|
199
|
+
assert_equal obj, [6,8]
|
200
|
+
assert_equal [6,8], obj
|
201
|
+
assert_equal [6,8,9], obj+[9]
|
202
|
+
assert obj.respond_to?(:to_ary)
|
203
|
+
assert obj.respond_to?(:map)
|
204
|
+
refute obj.respond_to?(:map!)
|
205
|
+
refute obj.respond_to?(:naiyo)
|
206
|
+
assert_raises(NoMethodError){ obj.push 5 }
|
207
|
+
assert_raises(NoMethodError){ obj.keep_if{} }
|
208
|
+
end
|
209
|
+
|
210
|
+
end # class TestUnitSubObject < MiniTest::Test
|
211
|
+
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sub_object
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Masa Sakano
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Class SubObject that is the parent class for Ruby SubString and SubArray
|
14
|
+
classes and alike, providing the base interface. This and child classes use negligible
|
15
|
+
memory space, as their instance holds the positional information only. It behaves
|
16
|
+
exactly like the source object (duck-typing), except destructive modification is
|
17
|
+
prohibited. If the original source object is destructively altered, the corresponding
|
18
|
+
instance can detect it and issue warning.
|
19
|
+
email:
|
20
|
+
executables: []
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.en.rdoc
|
24
|
+
files:
|
25
|
+
- ".gitignore"
|
26
|
+
- LICENSE.txt
|
27
|
+
- Makefile
|
28
|
+
- README.en.rdoc
|
29
|
+
- Rakefile
|
30
|
+
- lib/sub_object.rb
|
31
|
+
- lib/sub_object/sub_array.rb
|
32
|
+
- sub_object.gemspec
|
33
|
+
- test/test_sub_object.rb
|
34
|
+
homepage: https://www.wisebabel.com
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata:
|
38
|
+
yard.run: yri
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- "--charset=UTF-8"
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '2.0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubygems_version: 3.0.3
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: Parent class for memory-efficient sub-Something
|
59
|
+
test_files:
|
60
|
+
- test/test_sub_object.rb
|