primitive_wrapper 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +491 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/primitive_wrapper.rb +610 -0
- data/lib/primitive_wrapper/version.rb +3 -0
- data/primitive_wrapper.gemspec +38 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9201093aa1a6b289a8daec63a5bb81db853ac54f
|
4
|
+
data.tar.gz: eb6ebb33dd2c259832b1678d24f22892b776e13d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 31a190053179c69be065b30386ef83e4d300b39494a04f1cbce428df7f2b2083f21105a2d682ef04ec2a98ec311517f5c9e7130875ab7d431a63f339e52d562c
|
7
|
+
data.tar.gz: cd5a4c4a12b8a2bbc53d5050431cb1b6bafd55afc8febb0c914556466f0aeaf091237f4aadb1880e25124305badf3cf2f49a78abc06787ec9cd756ea52f24243
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at bryan@bdlsys.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Bryan Colvin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,491 @@
|
|
1
|
+
# PrimitiveWrapper
|
2
|
+
|
3
|
+
This gem creates a thin shell to encapsulate primitive literal types such as integers, floats and symbols.
|
4
|
+
There are a family of wrappers which mimic the behavior of what they contain.
|
5
|
+
Primitive types have several drawbacks: no constructor to call, can't create instance variables, and can't create singleton methods.
|
6
|
+
There is some utility in wrapping a primitive type. You can simulate a call by reference for example.
|
7
|
+
You can also simulate mutability, and pointers.
|
8
|
+
Some wrappers are dedicated to holding a single type while others may hold a family of types such as the `Number` wrapper.
|
9
|
+
What is interesting to note is Number objects do not derive from `Numeric`, but instead derive from `Value` (the wrapper base class);
|
10
|
+
but at the same time, `Number` objects mimic the methods of `Fixnum`, `Complex`, `Float`, etc.
|
11
|
+
Many of the wrappers can be used in an expression without having to call an access method.
|
12
|
+
There are also new types: `Bool` which wraps `true,false` and `Property` which wraps `Hash` types.
|
13
|
+
The `Property` object auto-methodizes the key names of the `Hash`.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'primitive_wrapper'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install primitive_wrapper
|
30
|
+
|
31
|
+
Or try it out inside irb:
|
32
|
+
|
33
|
+
$ require "primitive_wrapper"
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
There are 12 wrapper classes that hold these pesky primitive literal objects.
|
38
|
+
Unlike other class objects, these primitives have no constructor and attempt to look the the bottom turtle literals used in compiled languages like C/C++.
|
39
|
+
Such primitive literals are completely immutable. The wrappers used in this gem transform these primitive types into mutable objects.
|
40
|
+
In fact, these objects cannot be frozen as that would defeat their purpose. The freeze method on these wrapper classes is disabled.
|
41
|
+
The wrapped literal now behaves like a true ruby object such as Array, or Hash.
|
42
|
+
See the example below:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
five = Integer.new(5) # ERROR :: undefined method `new' for Integer:Class
|
46
|
+
five = Fixnum.new(5) # ERROR :: undefined method `new' for Fixnum:Class
|
47
|
+
five = 5 # ok, this one works!
|
48
|
+
|
49
|
+
def five.tripple
|
50
|
+
self + self + self
|
51
|
+
end # ERROR :: TypeError: can't define singleton
|
52
|
+
|
53
|
+
require "primitive_wrapper"
|
54
|
+
|
55
|
+
five = Int.new(5) # we now have a wrapped Integer object
|
56
|
+
five + 5 + five # 15 ... wrapper has the methods of an Integer object and can be used in an expression
|
57
|
+
|
58
|
+
def five.tripple
|
59
|
+
self + self + self
|
60
|
+
end
|
61
|
+
|
62
|
+
five.tripple # 15
|
63
|
+
|
64
|
+
five.kind_of? Integer # false
|
65
|
+
five.kind_of? Value # true
|
66
|
+
|
67
|
+
5.kind_of? Integer # true
|
68
|
+
5.kind_of? Value # false
|
69
|
+
```
|
70
|
+
|
71
|
+
As we can see from the above code, the Int class does not derive from Integer or anything that would appear numeric.
|
72
|
+
Our `Int` class is simply a container which happens to have the methods of `Integer` but does not derive itself from `Integer`.
|
73
|
+
All of these container objects derive from the Value class.
|
74
|
+
|
75
|
+
#### Object methods added
|
76
|
+
|
77
|
+
There are two new instance methods added to the base Object class necessary to implement this system.
|
78
|
+
The most important method is the `#to_wrapper` method.
|
79
|
+
When an instance variable calls this method, one of the wrapper containers will generate a new instance.
|
80
|
+
The one that gets picked is the one best suited for usability.
|
81
|
+
The other method named `#prim_value` will return the inner container value or simply return `self` if it is not a container derived from the `Value` class.
|
82
|
+
This is mostly used internally by the container objects. Below shows which objects get mapped to which containers:
|
83
|
+
|
84
|
+
```
|
85
|
+
nil => Bit
|
86
|
+
true, false => Bool
|
87
|
+
Integer => Int
|
88
|
+
Float => FloatW
|
89
|
+
Complex => Number
|
90
|
+
Rational => Number
|
91
|
+
Symbol => SymbolW
|
92
|
+
Hash => Property
|
93
|
+
String => Datum
|
94
|
+
```
|
95
|
+
|
96
|
+
Some examples are as follows:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
n = nil.to_wrapper # n.class == Bit
|
100
|
+
b = true.to_wrapper # b.class == Bool
|
101
|
+
y = 13.to_wrapper # y.class == Int
|
102
|
+
t = 3e-9.to_wrapper # t.class == FloatW
|
103
|
+
z = (1+6i).to_wrapper # z.class == Number
|
104
|
+
str = "yo!".to_wrapper # str.class == Datum
|
105
|
+
me = :me.to_wrapper # me.class == SymbolW
|
106
|
+
prop = {}.to_wrapper # prop.class == Property
|
107
|
+
aw =[1,2,3].to_wrapper # aw.class == Value ... everything else goes inside the Value container
|
108
|
+
```
|
109
|
+
|
110
|
+
The next sections will detail which objects are allowed to be contained within each container objects.
|
111
|
+
|
112
|
+
#### Value class container
|
113
|
+
|
114
|
+
This is the base class for all the containers created by this gem. Any object or primitive type can be held in this container.
|
115
|
+
To access the contents, simply call `#val`, `#prim_val`, or `#unwrap`.
|
116
|
+
Note that only `Value` derived objects can call `#val` while every object can call `#prim_val`.
|
117
|
+
You can also replace the contents with the `#replace` method or the `#val=` method.
|
118
|
+
The `Value` container has no restrictions on what it can hold. Other wrappers will raise an exception if the wrong type is added.
|
119
|
+
The comparison binary operators `==`, and `!=` are used to compare equality and inequality; this is done on the inner contained element.
|
120
|
+
Note that arrays are already containers, so there is little utility in placing it in another container.
|
121
|
+
The value container can only contain a single object; it is like an Array object that is only allowed to hold one item.
|
122
|
+
See the example code below:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
require 'PrimitiveWrapper'
|
126
|
+
|
127
|
+
# simulation of a pointer
|
128
|
+
|
129
|
+
a = Value.new(15)
|
130
|
+
b = a
|
131
|
+
b.val # 15
|
132
|
+
a.val # 15
|
133
|
+
a.val = 2 # assign new inner value
|
134
|
+
b.val # a == 2 ... a,b point to the same data
|
135
|
+
|
136
|
+
# simulation of pass by reference
|
137
|
+
|
138
|
+
def pass_by_reference(a, ref)
|
139
|
+
ref.val += 15 + a
|
140
|
+
return :yes
|
141
|
+
end
|
142
|
+
|
143
|
+
t = pass_by_reference(1, bjc = Value.new(10)) # t==:yes, bjc.val==26
|
144
|
+
```
|
145
|
+
|
146
|
+
The Value class has the following instance methods:
|
147
|
+
```ruby
|
148
|
+
:val, :prim_val, :unwrap, :~ # Returns the internal data object (~ overriden by Int)
|
149
|
+
:val=, :replace # Sets the internal data object
|
150
|
+
:valid_type(inst) # Returns true if `inst` variable is compatible to wrapper object
|
151
|
+
# Child classes override this method
|
152
|
+
:freeze # Does nothing ... defined to prevent Object#freeze from being called
|
153
|
+
:== # Compares equality of the inner elements
|
154
|
+
:!= # Compares inequality of the inner elements
|
155
|
+
:inspect # "(Value)==>data" where Value is the class name of the Value derived object, and data is #val.inspect
|
156
|
+
:to_s # returns #to_s on contained entity
|
157
|
+
```
|
158
|
+
|
159
|
+
#### Bool class container
|
160
|
+
|
161
|
+
The Bool container is allowed to contain only `true` or `false` primitive literals.
|
162
|
+
The container adds three binary operators: `&`, `|`, `^` and the prefix operator `!`.
|
163
|
+
Equality operators are inherited from the base class `Value`.
|
164
|
+
When an operator is called, a new Bool type is created.
|
165
|
+
Most of the other wrapper classes will return a standard type in order to make expressions less promblematic.
|
166
|
+
You can mix Bool types with `true` and `false` in an expression, but you must make sure the Bool type comes first or unexpected results may occur.
|
167
|
+
As with all wrapped containers, left-to-right expressions are owned by the left object. If this cannot be avoided, then calling `#val` will solve the problem.
|
168
|
+
This class is derived from Value. See the example below:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
aaa = Bool.new(true)
|
172
|
+
bbb = Bool.new(false)
|
173
|
+
|
174
|
+
aaa & !bbb | bbb & !aaa == aaa ^ bbb # definition of XOR
|
175
|
+
|
176
|
+
t = aaa ^ true # creates a new Bool type with value of false
|
177
|
+
t = bbb ^ true # creates a new Bool type with value of true
|
178
|
+
t = true ^ aaa # incorrect order returns false ... not a Bool type
|
179
|
+
t = true ^ bbb # incorrect order returns false ... not a Bool type
|
180
|
+
t = true ^ "?" # false ... any object will return false except nil and false
|
181
|
+
t = true ^ 0 # false
|
182
|
+
t = true ^ false # true ... this is why Bool must come first when mixing Bool with true or false
|
183
|
+
t = true ^ nil # true ... one of the only two primitive types that will provide a true result
|
184
|
+
|
185
|
+
# other methods
|
186
|
+
.to_i # returns 0 if false, 1 if true
|
187
|
+
.to_int # returns an Int object wrapper filled with either 0 or 1
|
188
|
+
.val # returns true or false
|
189
|
+
```
|
190
|
+
Note that the `~` prefix operator returns the primitive value.
|
191
|
+
The operator was also added to: Symbol, TrueClass, FalseClass, and NilClass which simply return self;
|
192
|
+
these classes previously did not define this operator. Only Integer and Int derived objects define this as being a one's complement operator.
|
193
|
+
This `~` prefix operator permits a terse way of getting to the primitive and can be used by Value derived objects or primitives described above.
|
194
|
+
This is necessary when using Bool types in conditional statements. Alternatively, you can use the #val method.
|
195
|
+
|
196
|
+
#### Bit class container
|
197
|
+
|
198
|
+
The `Bit` container is derived from Bool, but overrides the operators to return primitive values.
|
199
|
+
The Bit container also allows `nil` as a valid container entity. Bit objects are therefore thinner than Bool objects
|
200
|
+
and are most likely easier to work in expressions. There are also `FixedBit` derived classes called: `Null`, `TrueW`, `FalseW`.
|
201
|
+
These are essentially `Bit` objects restricted to a single type. See the example below:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
a = Bit.new(nil)
|
205
|
+
aa = Null.new # a==aa ... ~a => nil ~aa => nil ~nil => nil
|
206
|
+
|
207
|
+
b = Bit.new(true)
|
208
|
+
bb = TrueW.new # b==bb ... ~b => true ~bb => true ~true => true
|
209
|
+
|
210
|
+
c = Bit.new(false)
|
211
|
+
cc = FalseW.new # c==cc ... ~c => false ~bb => false ~false => false
|
212
|
+
|
213
|
+
bb ^ b # false
|
214
|
+
y = ~b ? 3:4 # y == 3 ... ~b shortcut for b.val
|
215
|
+
|
216
|
+
FixedBit.new # *** RuntimeError Exception: FixedBit cannot create instance
|
217
|
+
aa.val = nil # *** RuntimeError Exception: can't assign primitive type
|
218
|
+
```
|
219
|
+
|
220
|
+
#### Int class container
|
221
|
+
|
222
|
+
The `Int` container looks like a duck, quacks and waddles like a duck, swims like a duck, but it ain't a duck! The ancestor chain of `Int` only shares `Value`
|
223
|
+
and does not include `Numeric`. With a little meta-magic, the `Int` class mimics the methods of `Integer` so that it can be directly used
|
224
|
+
within an expression or without having to call `#val` to get the contained entity. Note that `~` does not get the contained entity, but instead returns the one's complement.
|
225
|
+
You can have your cake and eat it too by using two tildes `~~`. This undoes the one's complement and results in the primitive value.
|
226
|
+
The `#to_int` method on `Int` returns self, while `#to_i` accesses the primitive value.
|
227
|
+
Most of the time if `Integer` does not recognize something it calls coerce on the foreign object (which we grabbed from Integer).
|
228
|
+
This means that most expressions should work in whatever order you wish: if not, you will have to convert the Int object into an Integer object.
|
229
|
+
Pretty much all expressions with Int will create a primitive Integer.
|
230
|
+
See the example below:
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
aaa = 100.to_wrapper # method added to Object
|
234
|
+
bbb = Int.new(200) # standard constructor
|
235
|
+
|
236
|
+
ttt = aaa + bbb # ttt==300 ... ttt.class == Fixnum
|
237
|
+
~aaa # -101
|
238
|
+
~~aaa # 100
|
239
|
+
|
240
|
+
aaa.to_int # returns self, an Int object
|
241
|
+
aaa.to_i # returns 100
|
242
|
+
100.to_i # returns 100
|
243
|
+
100.to_int # returns 100
|
244
|
+
3 + aaa # returns 103
|
245
|
+
aaa + 3 # returns 103
|
246
|
+
```
|
247
|
+
|
248
|
+
#### FloatW class container
|
249
|
+
|
250
|
+
The `FloatW` container wraps `Float` types and possesses the thin wrapper similar to how `Int` works.
|
251
|
+
This can also be directly used within an expression. Only the `Float` derived objects may be placed inside this container.
|
252
|
+
The wrapper is so thin, you might think that you are actually a `Float` type.
|
253
|
+
See the example below:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
aaa = 3.1415926.to_wrapper
|
257
|
+
bbb = FloatW.new(3e0)
|
258
|
+
|
259
|
+
aaa.to_i # 3
|
260
|
+
aaa.to_int #
|
261
|
+
aaa ** bbb # 31.006275093569673
|
262
|
+
aaa.to_i # 3
|
263
|
+
aaa.to_int # (Int==>3)
|
264
|
+
```
|
265
|
+
|
266
|
+
#### Number class container
|
267
|
+
|
268
|
+
The `Number` container object can contain any object that is derived from `Numeric`.
|
269
|
+
This includes `Float`, `Fixnum`, `Bignum`, `Complex`, `Rational`, or even something that you create yourself so long as it derives from `Numeric`.
|
270
|
+
The class generator for `Number` grabs method names from all the standard library of Numeric derived classes.
|
271
|
+
This means that there is a possibility that the contained entity will not support some of the method calls.
|
272
|
+
This is no different than calling an undefined method. `Int`, `FloatW`, `Number`, `Datum` all derive from `ValueAdd` which is a child of `Value`.
|
273
|
+
You can't create an instance of `ValueAdd`, but it is used to store common methods as well as some class construction methods.
|
274
|
+
These will be described later on. This will come handy if you have a custom `Numeric` class and wish to import some of the methods defined there.
|
275
|
+
This works just like the other specific number wrappers previously described.
|
276
|
+
|
277
|
+
#### Datum class container
|
278
|
+
|
279
|
+
The `Datum` container holds everything a `Number` can hold and allows `String` data to be added. This represents things that a human can type into a form as a general data thing.
|
280
|
+
The methods of `String` are also added. Errors will occur if you try to call string methods on a `Datum` that is holding a number and visa versa.
|
281
|
+
|
282
|
+
#### SymbolW class container
|
283
|
+
|
284
|
+
The `SymbolW` class container holds a `Symbol` primitive. It derives directly from `Value`.
|
285
|
+
Although rarely used, `Symbol` types do have some immutable methods that have some string-like behavior.
|
286
|
+
The `SymbolW` class implements most of these, and adds some mutating methods as well.
|
287
|
+
This makes the SymolW object appear to be completely mutable; however, the inner element remains immutable.
|
288
|
+
See below:
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
# non-mutating methods taken from Symbol:
|
292
|
+
# :<, :<=, :>, :>=, :[], :between?, :capitalize, :downcase, :empty?, :length, :next, :size, :succ, :swapcase, :upcase
|
293
|
+
|
294
|
+
# non-mutating methods taken from String:
|
295
|
+
# :append, :include?, :prepend ... actually append renamed from :<<
|
296
|
+
|
297
|
+
# mutating methods of SymbolW
|
298
|
+
# :[]=, :append!, :capitalize!, :downcase!, :next!, :prepend!, :replace, :succ!, :swapcase!, :upcase!
|
299
|
+
|
300
|
+
# construction:
|
301
|
+
wsym = :my_symbol.to_wrapper
|
302
|
+
tsym = SymbolW.new(:my_other_symbol)
|
303
|
+
|
304
|
+
# example:
|
305
|
+
wsym[1] = :e # wsym == (SymbolW==>:me_symbol)
|
306
|
+
```
|
307
|
+
|
308
|
+
#### Property class container
|
309
|
+
|
310
|
+
The `Property` class container holds a Hash object.
|
311
|
+
You can also place a `Hash` object inside the `Value` container, but there are no exposed methods there.
|
312
|
+
You can initialize the property class with a Hash instance, or start with a blank slate.
|
313
|
+
What is cool about the Property object is the internal hash keys become setters and getters on the instance so long as they obey the rules.
|
314
|
+
Additionally the internal hash is the original hash and not a copy;
|
315
|
+
so if the contained hash object is changed outside of the Property container,
|
316
|
+
new access methods will automatically be generated.
|
317
|
+
Hash keys that comprise upper and lowercase characters, underscore, and digits (with the first character not a number)
|
318
|
+
will become methods on the `Property` instance.
|
319
|
+
Note that these names cannot compete with existing instance method names of Object or Property including private methods.
|
320
|
+
Nested hierarchical names are not added: only the first-level names.
|
321
|
+
Non-compliant names are still added to the internal `Hash` instance; they just don't have the corresponding method names.
|
322
|
+
Another bonus is we have an unmolested `Hash` unlike other competing gems that trash the `Hash` with property getters and setters.
|
323
|
+
Most Hash methods except for `:[], :[]=` have not been implemented in order to allow for more property names.
|
324
|
+
You can quickly get to the internal Hash by using the `~` tilde prefix operator however.
|
325
|
+
See the code below:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
abc = Property.new
|
329
|
+
xyz = Property.new {:fred => "freddy", :george => "georgey"}
|
330
|
+
|
331
|
+
abc.seven = 7
|
332
|
+
abc.property? :seven # true
|
333
|
+
abc.seven # 7
|
334
|
+
abc.val # {:seven => 7}
|
335
|
+
abc.to_s=15 # this won't create a property because :to_s is reserved
|
336
|
+
abc.property? :to_s # false
|
337
|
+
abc.deferred? :to_s # true ... internal hash owns this, but no property is created
|
338
|
+
abc.to_s # "{:seven=>7, :to_s=>15}"
|
339
|
+
|
340
|
+
abc.defined_properties! # [:seven]
|
341
|
+
xyz.defined_properties! # [:fred, :george]
|
342
|
+
abc.deferred_properties! # [:to_s]
|
343
|
+
xyz.deferred_properties! # []
|
344
|
+
|
345
|
+
abc.keys # NoMethodError Exception: undefined method `keys' for (Property==>{...}):Property
|
346
|
+
(~abc).keys # [:seven, :to_s] ... Note ~abc.keys does not work as this is interpreted as: ~(abc.keys)
|
347
|
+
abc.val.keys # [:seven, :to_s] ... probably best syntax
|
348
|
+
hash = ~abc # grab internal Hash object
|
349
|
+
hash[:cool] = "yes!"
|
350
|
+
abc.cool # "yes!" ... wrapper behavior looks pointer-ish
|
351
|
+
abc[:works2] = "yep!"
|
352
|
+
abc.works2 # "yep!" ... wrapper supports array access
|
353
|
+
abc[:seven] # 7
|
354
|
+
abc.sam = {}
|
355
|
+
abc.defined_properties! # [:cool, :sam, :seven, :works2]
|
356
|
+
abc.val = {}
|
357
|
+
abc.defined_properties! # [] ... you just zapped the entire hash after abc.val = {}
|
358
|
+
|
359
|
+
# Property Instance methods
|
360
|
+
# :~ :unwrap :val :prim_value ... returns internal hash
|
361
|
+
# :val= :replace ... assigns new hash to wrapper
|
362
|
+
# :[] :[]= ... access internal hash element
|
363
|
+
# :deferred? key ... true if key in in internal hash but will not create a property access variable
|
364
|
+
# :property? key ... true if key is registered as a property access variable
|
365
|
+
# :split! ... returns an array of two Hash instances with [valid_property_hash, deferred_hash]
|
366
|
+
# :rekey! ... rekeys property and deferred keys ... called internally
|
367
|
+
# :valid_type inst_var ... used internally for #replace or #val= ... true if Hash or Property instance
|
368
|
+
# :method_missing ... used internally to simulate property methods
|
369
|
+
# :import_hash! ext_hash ... merge foreign hash into self
|
370
|
+
# :ensure_valid ... used internally ... raises error on type mismatch from :valid_type
|
371
|
+
# :deferred_properties! ... returns list of keys that do not have property methods
|
372
|
+
# :defined_properties! ... returns list of keys that have property methods
|
373
|
+
# :define_properties! [] , dflt ... assignes default properties to a list of property keys
|
374
|
+
|
375
|
+
# Property Class methods
|
376
|
+
# ::good_candidate_name? name ... test to see if name is formatted corectly, and is of type Hash
|
377
|
+
# ::good_key? name ... true if properly formatted and not a reserved word
|
378
|
+
# ::bad_key? name ... true if wrong type, or reserved word, or not formatted correctly
|
379
|
+
# ::reserve_property_names! [] ... adds additional reserved words
|
380
|
+
```
|
381
|
+
|
382
|
+
#### Customization
|
383
|
+
|
384
|
+
Things should work out of the box, until they don't. This section will detail what customization is possible with what container.
|
385
|
+
First, Property has one method to reserve allowed property key names. This is done as follows:
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
Property.reserve_property_names! [:Fred, :Wilma, :BamBam, :Barney, :Betty, :Pebbles]
|
389
|
+
|
390
|
+
bedrock = Property.new
|
391
|
+
bedrock.Fred = "Yaba Daba Doo!"
|
392
|
+
bedrock.property? :Fred # false
|
393
|
+
bedrock.deferred? :Fred # true
|
394
|
+
bedrock[:Fred] # "Yaba Daba Doo!"
|
395
|
+
bedrock.Fred # *** NoMethodError Exception: undefined method `Fred' for (Property==>{...}):Property
|
396
|
+
bedrock.Test = "pass"
|
397
|
+
bedrock.Test # "pass"
|
398
|
+
```
|
399
|
+
|
400
|
+
The containers `Int`, `FloatW`, `Number`, and `Datum` all derive from `ValueAdd` which has two class methods.
|
401
|
+
The `ValueAdd` class derives from `Value`, but cannot create an instance. Its purpose is to generate methods
|
402
|
+
that belong somewhere else. If we peer into the code we see this:
|
403
|
+
|
404
|
+
```ruby
|
405
|
+
class ValueAdd < Value
|
406
|
+
def valid_type(prm) # must override
|
407
|
+
false
|
408
|
+
end
|
409
|
+
def self.bestow_methods(*args)
|
410
|
+
args = args.first if args.first.kind_of? Array
|
411
|
+
args.each do |meth|
|
412
|
+
define_method meth do |*args, &block|
|
413
|
+
@value.send(meth, *args, &block)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
def self.capture_base_methods(type, except=Object)
|
418
|
+
add_me = type.instance_methods - except.instance_methods - Value.instance_methods - [:singleton_method_added]
|
419
|
+
bestow_methods add_me
|
420
|
+
end
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
424
|
+
If we call `Int.capture_base_methods(Fixnum)`, all of the methods that a Fixnum can do is bestowed to the Int class.
|
425
|
+
If you define a new method to Integer called `:do_my_math_thing`, you can teach Int to do it by calling `Int.bestow_methods(:do_my_math_thing)`.
|
426
|
+
You should also add it to `Number` and `Datum`. Let's say you have a really cool Matrix class that you would like to teach the Number wrapper.
|
427
|
+
Here is how you would do it:
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
class Number
|
431
|
+
capture_base_methods(Matrix, self)
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
Some of the most extensive customizations involve the SymbolW wrapper class. Only a small subset of `String` methods were added to this wrapper.
|
436
|
+
They were chosen to complement the non-mutating `String` like functions already available to Symbols.
|
437
|
+
For example, `#upcase!` was added to complement `#upcase`. Note that the Symbol class does not and cannot have a mutating method.
|
438
|
+
There are several Class methods on `SymbolW` that capture `Symbol` and `String` methods in several configurable ways.
|
439
|
+
If we look under the hood at the source code we will see how they got defined by example:
|
440
|
+
|
441
|
+
```ruby
|
442
|
+
# ClassW class methods:
|
443
|
+
#
|
444
|
+
# -- single defines
|
445
|
+
# ::bestow_symbol_method(meth_def_name, meth_call_name, mutate_self=false)
|
446
|
+
# ::bestow_string_non_mutate_method(meth_def_name, meth_call_name, return_type = SymbolW)
|
447
|
+
# ::bestow_string_mutate_method(meth_def_name, meth_call_name)
|
448
|
+
#
|
449
|
+
# -- group defines
|
450
|
+
# ::bestow_string_mutate_methods(meths)
|
451
|
+
# ::bestow_string_non_mutate_methods(meths, return_type = SymbolW)
|
452
|
+
# ::bestow_symbol_methods(meths, mutate_self=false)
|
453
|
+
#
|
454
|
+
```
|
455
|
+
The group defines are used when you don't need to rename a method and wish to install a bunch of them.
|
456
|
+
As an example `SymbolW.bestow_symbol_methods [:next, :succ, :[], :length, :size, :upcase, :downcase, ...]` was called to install the standard set of methods that exist on `Symbol`.
|
457
|
+
The last parameter when set to true will mutate the inner Symbol which means that it gets replaced with the result of the method.
|
458
|
+
Some methods that do not return a string like `#length` should not be used as a source for mutating methods.
|
459
|
+
Passing `true` as the second parameter will auto-bang the name turning `#upcase` into `#upcase!`. The singular version of the method gives you more control allowing you to completely rename the method.
|
460
|
+
The number of Symbol methods is fairly small when compared to the number of String methods.
|
461
|
+
It was intentional to leave most of these open as it there are many ways to transform these methods. The self mutating version is the most straight forward as we don't need to worry about the return type.
|
462
|
+
See the code below to see how it was used:
|
463
|
+
|
464
|
+
```ruby
|
465
|
+
SymbolW.bestow_string_mutate_method(:prepend!, :prepend)
|
466
|
+
SymbolW.bestow_string_mutate_method(:append!, :<<)
|
467
|
+
SymbolW.bestow_string_mutate_method(:[]=, :[]=)
|
468
|
+
```
|
469
|
+
|
470
|
+
Looking at the code, we see that the `#append!` method is a renamed version of `:<<`. What is interesting to note is that both in the `String` and the `Symbol` classes `#append` is not found.
|
471
|
+
Only string implements `:<<` which is a binary operator that does not mutate. Because we are bestowing this self-mutating method to `SymbolW`, we don't need to consider what the return type is as `self` is the only logical choice.
|
472
|
+
The non-mutating version must choose what kind of type is returned. We have four choices for return types: (1) force a conversion to `SymbolW`, (2) return `Symbol`, (3) return `String`, or (4) return whatever the string method would otherwise return.
|
473
|
+
We would choose the 4th version for any non-string return method such as `#match`. To select this we pass `nil` to the return type which says let the string method decide.
|
474
|
+
Now we are left with the head-scratcher on how to decide which is the best return type to give to a string method bestowed upon a wrapped symbol thing.
|
475
|
+
This can be mostly a matter of taste, but we can choose custom names that convey this information. Now if you have an extension library such as used by the `gstring` gem, you have an even larger list to choose from.
|
476
|
+
So it is left to you to decide.
|
477
|
+
|
478
|
+
## Development
|
479
|
+
|
480
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
481
|
+
|
482
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
483
|
+
|
484
|
+
## Contributing
|
485
|
+
|
486
|
+
I need to control this for the time being. You are welcome to shoot me an EMAIL if you have any issues or suggestions.
|
487
|
+
|
488
|
+
## License
|
489
|
+
|
490
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
491
|
+
|