flex_coerce 0.0.1
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 +17 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +4 -0
- data/flex_coerce.gemspec +27 -0
- data/lib/flex_coerce.rb +6 -0
- data/lib/flex_coerce/flex_coerce.rb +37 -0
- data/lib/flex_coerce/version.rb +3 -0
- data/spec/flex_coerce_spec.rb +123 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 24baca495e6e4c9a7f8d1da2f74de3166751db50
|
4
|
+
data.tar.gz: 7707c8373d1dd02a1d2f4a3e387752c3036e16fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6f8bf58b33f181f6060d97d6d3f6cb802c7b1f96cf23cd6b59ce30214b216edc5069f23c15b830f7b8b830baeb9f2ddd2dca51d66a246f0bb14d88640748337e
|
7
|
+
data.tar.gz: c987e94f52aeadd3b2dbf2da9856ff41accbacfc65bf71204467e08d67d490f27abf9d56eec4255a422496c54f6f12530732f957cd045648a0869bbf0dd3be81
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ilya Vorontsov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# FlexCoerce
|
2
|
+
|
3
|
+
FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: `==`, `===`).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Attention: this gem works only with Ruby 2.0 because it uses Module#prepend which has no analogs in Ruby 1.9. If you have an idea how to realize it on 1.9 without too much mess - your pull-requests are welcome.
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'flex_coerce'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install flex_coerce
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
I'll show you an example of (very simplistic) class Unit for physical units. We can multiply numbers with units but can't add them.
|
24
|
+
|
25
|
+
```
|
26
|
+
require 'flex_coerce'
|
27
|
+
class Unit
|
28
|
+
attr_reader :num, :unit
|
29
|
+
def initialize(num, unit)
|
30
|
+
@num, @unit = num, unit
|
31
|
+
end
|
32
|
+
def +(other)
|
33
|
+
raise TypeError, 'Can\'t add Unit to a non-Unit' unless other.is_a? Unit
|
34
|
+
raise ArgumentError, 'Can\'t sum units of different types' unless other.unit == unit
|
35
|
+
Unit.new(num + other.num, unit)
|
36
|
+
end
|
37
|
+
def *(other)
|
38
|
+
return self * Unit.new(other, '1') if other.is_a? Numeric
|
39
|
+
Unit.new(num * other.num, "#{unit}*#{other.unit}")
|
40
|
+
end
|
41
|
+
def coerce(other, meth = nil)
|
42
|
+
raise TypeError, 'Coercion error: can\'t coerce non-numeric class to a Unit' unless other.is_a? Numeric
|
43
|
+
case meth.to_sym
|
44
|
+
when :*
|
45
|
+
[Unit.new(other,'1'), self]
|
46
|
+
when :+
|
47
|
+
raise TypeError 'Coercion error: can\'t compare Unit to a non-unit anything'
|
48
|
+
[other, self]
|
49
|
+
else
|
50
|
+
raise TypeError, 'Unsupported operation'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
prepend FlexCoerce
|
55
|
+
end
|
56
|
+
|
57
|
+
5 * Unit.new(3,'cm') # ==> Unit(15,'1*cm')
|
58
|
+
5 + Unit.new(3,'cm') # ==> raises an Error
|
59
|
+
```
|
60
|
+
|
61
|
+
You define your own `#coerce` with two arguments: standard argument `other` and new argument `meth` which represents the binary operations caused coercion. All the magic is in `prepend FlexCoerce` call.
|
62
|
+
It decorates your `#coerce` method with another one, which follow ruby conventions about coerce method. This is done by creating an instance of `CoerceableWrapper` class which takes a reference to left-side object. This intermediate class defines its own methods for binary operators such that they play well with `#coerce` method of right-side object class. I should mention that this method works good for missing, optional or required second argument of `#coerce`, you can find examples in specs.
|
63
|
+
|
64
|
+
Possibly this example is too simplistic and can be made in another and more consistent way. But this is a proof of concept that coerce can be made method-specific without touching base ruby classes. You're welcome with more interesting examples and more powerful and clear design.
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
1. Fork it
|
69
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
71
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
72
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/flex_coerce.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'flex_coerce/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "flex_coerce"
|
8
|
+
spec.version = FlexCoerce::VERSION
|
9
|
+
spec.authors = ["Ilya Vorontsov"]
|
10
|
+
spec.email = ["prijutme4ty@gmail.com"]
|
11
|
+
spec.description = %q{FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: `==`, `===`).}
|
12
|
+
spec.summary = %q{FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: `==`, `===`).}
|
13
|
+
spec.homepage = "https://github.com/prijutme4ty/flex_coerce"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency 'rspec', '>= 2.0'
|
24
|
+
spec.add_development_dependency 'rspec-given', '>= 2.0.0'
|
25
|
+
|
26
|
+
spec.required_ruby_version = '2.0.0'
|
27
|
+
end
|
data/lib/flex_coerce.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module FlexCoerce
|
2
|
+
def self.prepend_features(base)
|
3
|
+
base.instance_eval{
|
4
|
+
@__original_coerce_arity ||= base.instance_method(:coerce).arity
|
5
|
+
}
|
6
|
+
super
|
7
|
+
end
|
8
|
+
def coerce(other, meth = nil)
|
9
|
+
if meth
|
10
|
+
case self.class.class_eval{ @__original_coerce_arity }
|
11
|
+
when 1
|
12
|
+
super(other.obj)
|
13
|
+
when 2, -2
|
14
|
+
# coerce(other, meth) or coerce(other, meth = nil)
|
15
|
+
super(other.obj, meth)
|
16
|
+
else
|
17
|
+
raise 'Too many arguments for coerce'
|
18
|
+
end
|
19
|
+
else
|
20
|
+
[CoerceableWrapper.new(other), self]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This class is a wrapper for object which redefines methods leading to coercion
|
26
|
+
class CoerceableWrapper
|
27
|
+
attr_reader :obj
|
28
|
+
def initialize(obj)
|
29
|
+
@obj = obj
|
30
|
+
end
|
31
|
+
[:+, :-, :*, :/, :%, :div, :divmod, :fdiv, :**, :&, :|, :^, :>, :>=, :<, :<=, :<=>].each do |op|
|
32
|
+
define_method(op) do |other|
|
33
|
+
self_coerced, other_coerced = other.coerce(self, op)
|
34
|
+
self_coerced.send(op, other_coerced)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
$:.unshift '../lib/'
|
2
|
+
require 'flex_coerce'
|
3
|
+
require 'rspec/given'
|
4
|
+
|
5
|
+
class UnitNoOptionalArgument
|
6
|
+
attr_reader :num
|
7
|
+
def initialize(num) @num = num; end
|
8
|
+
def coerce(other) [other, self.num]; end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UnitBothRequired
|
12
|
+
attr_reader :num
|
13
|
+
def initialize(num) @num = num; end
|
14
|
+
def coerce(other, meth) [other, self.num]; end
|
15
|
+
end
|
16
|
+
|
17
|
+
class UnitWeakCheck
|
18
|
+
attr_reader :num
|
19
|
+
def initialize(num) @num = num; end
|
20
|
+
def coerce(other, meth = nil) [other, self.num]; end
|
21
|
+
end
|
22
|
+
|
23
|
+
class UnitStrongCheck
|
24
|
+
attr_reader :num
|
25
|
+
def initialize(num) @num = num; end
|
26
|
+
|
27
|
+
def coerce(other, meth = nil)
|
28
|
+
raise TypeError 'Coercing error' unless meth
|
29
|
+
case meth.to_sym
|
30
|
+
when :*, :/
|
31
|
+
[other, self.num]
|
32
|
+
when :+, :-
|
33
|
+
raise TypeError, 'Coercing error'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
describe FlexCoerce do
|
41
|
+
context 'with #coerce having the only required and no optional arguments' do
|
42
|
+
Given(:unit_class) { UnitNoOptionalArgument }
|
43
|
+
Given(:number){ 3 }
|
44
|
+
Given(:unit) { unit_class.new(2) }
|
45
|
+
context 'before prepending FlexCoerce' do
|
46
|
+
When(:plus_operation) { number + unit }
|
47
|
+
Then{plus_operation.should == 5}
|
48
|
+
When(:mul_operation) { number * unit }
|
49
|
+
Then{mul_operation.should == 6}
|
50
|
+
end
|
51
|
+
context 'after prepending FlexCoerce' do
|
52
|
+
When{ unit_class.send :prepend, FlexCoerce }
|
53
|
+
|
54
|
+
When(:plus_operation) { number + unit }
|
55
|
+
Then{plus_operation.should == 5}
|
56
|
+
When(:mul_operation) { number * unit }
|
57
|
+
Then{mul_operation.should == 6}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with #coerce having the only required and no optional arguments' do
|
62
|
+
context 'with coerce not checking method' do
|
63
|
+
Given(:unit_class) { UnitWeakCheck }
|
64
|
+
Given(:number){ 3 }
|
65
|
+
Given(:unit) { unit_class.new(2) }
|
66
|
+
context 'before prepending FlexCoerce' do
|
67
|
+
When(:plus_operation) { number + unit }
|
68
|
+
Then{plus_operation.should == 5}
|
69
|
+
When(:mul_operation) { number * unit }
|
70
|
+
Then{mul_operation.should == 6}
|
71
|
+
end
|
72
|
+
context 'after prepending FlexCoerce' do
|
73
|
+
When { unit_class.send :prepend, FlexCoerce }
|
74
|
+
|
75
|
+
When(:plus_operation) { number + unit }
|
76
|
+
Then{plus_operation.should == 5}
|
77
|
+
When(:mul_operation) { number * unit }
|
78
|
+
Then{mul_operation.should == 6}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'with coerce checking method' do
|
83
|
+
Given(:unit_class) { UnitStrongCheck }
|
84
|
+
Given(:number){ 3 }
|
85
|
+
Given(:unit) { unit_class.new(2) }
|
86
|
+
context 'before prepending FlexCoerce' do
|
87
|
+
When(:plus_operation) { number + unit }
|
88
|
+
Then{plus_operation.should have_failed}
|
89
|
+
When(:mul_operation) { number * unit }
|
90
|
+
Then{mul_operation.should have_failed}
|
91
|
+
end
|
92
|
+
context 'after prepending FlexCoerce' do
|
93
|
+
When { unit_class.send :prepend, FlexCoerce }
|
94
|
+
|
95
|
+
When(:plus_operation) { number + unit }
|
96
|
+
Then{plus_operation.should have_failed}
|
97
|
+
When(:mul_operation) { number * unit }
|
98
|
+
Then{mul_operation.should == 6}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with #coerce having two required arguments' do
|
104
|
+
Given(:unit_class) { UnitBothRequired }
|
105
|
+
Given(:number){ 3 }
|
106
|
+
Given(:unit) { unit_class.new(2) }
|
107
|
+
context 'before prepending FlexCoerce' do
|
108
|
+
When(:plus_operation) { number + unit }
|
109
|
+
Then{plus_operation.should have_failed}
|
110
|
+
When(:mul_operation) { number * unit }
|
111
|
+
Then{mul_operation.should have_failed}
|
112
|
+
end
|
113
|
+
context 'after prepending FlexCoerce' do
|
114
|
+
When { unit_class.send :prepend, FlexCoerce }
|
115
|
+
|
116
|
+
When(:plus_operation) { number + unit }
|
117
|
+
Then{plus_operation.should == 5}
|
118
|
+
When(:mul_operation) { number * unit }
|
119
|
+
Then{mul_operation.should == 6}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flex_coerce
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ilya Vorontsov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-given
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.0.0
|
69
|
+
description: 'FlexCoerce - is a gem which allow you create operator-dependent coercion
|
70
|
+
logic. It''s useful when your type should be treated in a different way for different
|
71
|
+
binary operations (including arithmetic operators, bitwise operators and comparison
|
72
|
+
operators except equality checks: `==`, `===`).'
|
73
|
+
email:
|
74
|
+
- prijutme4ty@gmail.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- flex_coerce.gemspec
|
85
|
+
- lib/flex_coerce.rb
|
86
|
+
- lib/flex_coerce/flex_coerce.rb
|
87
|
+
- lib/flex_coerce/version.rb
|
88
|
+
- spec/flex_coerce_spec.rb
|
89
|
+
homepage: https://github.com/prijutme4ty/flex_coerce
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.0.0
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.0.2
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: 'FlexCoerce - is a gem which allow you create operator-dependent coercion
|
113
|
+
logic. It''s useful when your type should be treated in a different way for different
|
114
|
+
binary operations (including arithmetic operators, bitwise operators and comparison
|
115
|
+
operators except equality checks: `==`, `===`).'
|
116
|
+
test_files:
|
117
|
+
- spec/flex_coerce_spec.rb
|