yopt 0.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 +11 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +206 -0
- data/Rakefile +39 -0
- data/bin/console +11 -0
- data/bin/setup +7 -0
- data/docs/README.template.md +207 -0
- data/docs/basics.rb +37 -0
- data/docs/enumerable_methods.md +21 -0
- data/docs/full_example_snippet.rb +51 -0
- data/docs/including yopt.md +8 -0
- data/docs/usage_reduce.rb +12 -0
- data/docs/usage_snippet.rb +77 -0
- data/docs/usage_zip.rb +30 -0
- data/lib/yopt.rb +88 -0
- data/lib/yopt/version.rb +3 -0
- data/yopt.gemspec +24 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: da7d8098ce8b1dcc66d5e8f6ba3baf208daf805d
|
4
|
+
data.tar.gz: 6ae68982bb5ff8a3dc09cba07961c04e51b46fa3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57fa46f4d6f766ac6fcd87a4b261fd049e8e3757c5578efa2851235b096f1086eb5cb1c801d0ca2dfa0266d6f5d593564677f4a170be22636cbcf3708517ddd9
|
7
|
+
data.tar.gz: 577ab29f9bdb5283d93941f24995c5d8b4cb02a7847faa2ff66e5018b8c7308f582adac0439ea1ff5c1dd7039fa68d32f8defed983b61dfd34122ddd0f043b0f
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 lorenzo.barasti
|
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,206 @@
|
|
1
|
+
# Yopt
|
2
|
+
|
3
|
+
A [Scala](http://www.scala-lang.org/api/current/index.html#scala.Option) inspired gem that introduces `Option`s to Ruby while aiming for an idiomatic API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'yopt'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install yopt
|
20
|
+
|
21
|
+
## Basic usage
|
22
|
+
|
23
|
+
The Option type models the possible absence of a value. It lets us deal with the uncertainty related to such a value being there without having to resort to errors or conditional blocks.
|
24
|
+
|
25
|
+
Instances of Option are either an instance of `Yopt::Some` - meaning the option contains a value - or the object `Yopt::None` - meaning the option is *empty*.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'yopt'
|
29
|
+
|
30
|
+
some = Yopt::Some.new(42)
|
31
|
+
none = Yopt::None
|
32
|
+
```
|
33
|
+
|
34
|
+
We can access and manipulate the optional value by passing a block to `Option#map`.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
some.map {|value| value + 2} # returns Some(44)
|
38
|
+
none.map {|value| value + 2} # returns None
|
39
|
+
```
|
40
|
+
|
41
|
+
When we are not interested in the result of a computation on the optional value, it is a good practice to use `Option#each` rather than `Option#map`. That will make our intention clearer.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
some.each {|value| puts value} # prints 42
|
45
|
+
none.each {|value| puts value} # does not print anything
|
46
|
+
```
|
47
|
+
|
48
|
+
We can safely retrieve the optional value by passing a default value to `Option#get_or_else`
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
some.get_or_else 0 # returns 42
|
52
|
+
none.get_or_else 0 # returns 0
|
53
|
+
```
|
54
|
+
|
55
|
+
We can also filter the optional value depending on how it evaluates against a block via `Option#select`
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
some.select {|value| value < 0} # returns None
|
59
|
+
none.select {|value| value < 0} # returns None
|
60
|
+
some.select {|value| value > 0} # returns Some(42)
|
61
|
+
```
|
62
|
+
|
63
|
+
We can easily turn any object into an Option by means of `Option.call` - aliased to `Option.[]` for convenience.
|
64
|
+
For instance, this is useful when dealing with functions that might return `nil` to express the absence of a result.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Yopt::Option[nil] # returns None
|
68
|
+
Yopt::Option[42] # returns Some(42)
|
69
|
+
```
|
70
|
+
|
71
|
+
|
72
|
+
A combination of the few methods just introduced already allows us to implement some pretty interesting logic. Checkout `basics.rb` in the docs folder to get some inspiration.
|
73
|
+
|
74
|
+
## Why opt?
|
75
|
+
|
76
|
+
Using `Option`s reduces the amount of branching in our code and lets us deal with exceptional cases in a seamless way. No more check-for-nil, no more `rescue` blocks, just plain and simple data transformation.
|
77
|
+
|
78
|
+
It also makes our code safer by treating *the absence of something* like a fully fledged object, and enables us to use the Null Object Pattern everywhere we want without the overhead of having to write specialized Null-type classes for different classes.
|
79
|
+
|
80
|
+
## Advanced Usage
|
81
|
+
### #reduce
|
82
|
+
Given an Option `opt`, a value `c` and a lambda `f`,
|
83
|
+
```
|
84
|
+
opt.reduce(c, &f)
|
85
|
+
```
|
86
|
+
returns `c` if `opt` is `None`, and `f.(c, opt)` otherwise.
|
87
|
+
|
88
|
+
This is a shortcut to
|
89
|
+
```
|
90
|
+
opt.map{|v| f.(c,v)}.get_or_else(c)`
|
91
|
+
```
|
92
|
+
|
93
|
+
|
94
|
+
### #flatten and #flat_map
|
95
|
+
When working with functions returning `Option`, we might end up dealing with nested options...
|
96
|
+
```ruby
|
97
|
+
maybe_sqrt = lambda {|x| Yopt::Option[x >= 0 ? Math.sqrt(x) : nil]}
|
98
|
+
maybe_increment = lambda {|x| Yopt::Option[x > 1 ? x + 1 : nil]}
|
99
|
+
|
100
|
+
maybe_sqrt.(4).map {|v| maybe_increment.(v)} # Some(Some(3.0))
|
101
|
+
maybe_sqrt.(1).map {|v| maybe_increment.(v)} # Some(None)
|
102
|
+
```
|
103
|
+
|
104
|
+
Usually, this is not what we want, so we call `Option#flatten` on the result
|
105
|
+
```ruby
|
106
|
+
maybe_sqrt.(4).map {|v| maybe_increment.(v)}.flatten # Some(3.0)
|
107
|
+
maybe_sqrt.(1).map {|v| maybe_increment.(v)}.flatten # None
|
108
|
+
```
|
109
|
+
|
110
|
+
`Option#flat_map` combines the two calls into one
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
maybe_sqrt.(4).flat_map {|v| maybe_increment.(v)} # Some(3.0)
|
114
|
+
maybe_sqrt.(1).flat_map {|v| maybe_increment.(v)} # None
|
115
|
+
```
|
116
|
+
|
117
|
+
A difference to keep in mind is that `#flatten` will raise an error if the wrapped value does not respond to `#to_ary`
|
118
|
+
```ruby
|
119
|
+
Yopt.Option[42].flatten # raises TypeError: Argument must be an array-like object. Found Fixnum
|
120
|
+
```
|
121
|
+
whereas #flat_map behaves like #map when the passed block does not return an array-like value
|
122
|
+
```ruby
|
123
|
+
Yopt.Option[42].flat_map{|v| v} # returns Some(42)
|
124
|
+
```
|
125
|
+
|
126
|
+
|
127
|
+
### #zip
|
128
|
+
When dealing with a set of `Option` instances, we might want to ensure that they are all defined - i.e. not __empty__ - before continuing a computation...
|
129
|
+
```ruby
|
130
|
+
email_opt.each(&send_pass_recovery) unless (email_opt.empty? or captcha_opt.empty?)
|
131
|
+
```
|
132
|
+
|
133
|
+
We can avoid `empty?` checks by using `Option#zip`
|
134
|
+
```ruby
|
135
|
+
email_opt.zip(captcha_opt).each{|(email,_)| send_pass_recovery(email)}
|
136
|
+
```
|
137
|
+
|
138
|
+
`Option#zip` returns `None` if any of the arguments is `None` or if the caller is `None`
|
139
|
+
```ruby
|
140
|
+
Yopt::None.zip Option.[42] # None
|
141
|
+
Option.[42].zip Yopt::None # None
|
142
|
+
Option.[42].zip Option.[0], Yopt::None, Option.[-1] # None
|
143
|
+
```
|
144
|
+
|
145
|
+
When both the caller and all the arguments are defined then `zip` collects all the values in an Array wrapped in a `Yopt::Some`
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
Option.[42].zip Option.[0], Option.["str"] # Some([42, 0, "str"])
|
149
|
+
```
|
150
|
+
|
151
|
+
|
152
|
+
### #grep
|
153
|
+
We often find ourselves filtering data before applying a transformation...
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
opt.filter {|v| (1...10).include? v}.map {|v| v + 1}
|
157
|
+
```
|
158
|
+
|
159
|
+
In this scenario, `Option#grep` can sometimes make the code more concise
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
opt.grep(1...10) {|v| v + 1}
|
163
|
+
```
|
164
|
+
|
165
|
+
`Option#grep` supports lambdas as well
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
is_positive = lambda {|x| x > 0}
|
169
|
+
|
170
|
+
opt.grep(is_positive) {|v| Math.log(v)}
|
171
|
+
# is equivalent to
|
172
|
+
opt.filter(&is_positive).map {|v| Math.log(v)}
|
173
|
+
```
|
174
|
+
|
175
|
+
|
176
|
+
## Haskell Data.Maybe cheat sheet
|
177
|
+
|
178
|
+
Some (None?) might enjoy a comparison with Haskell's [Maybe](https://hackage.haskell.org/package/base/docs/Data-Maybe.html). Here is how the Data.Maybe API translate to Yopt.
|
179
|
+
```ruby
|
180
|
+
maybe default f opt -> opt.map(&f).get_or_else(default)
|
181
|
+
isJust opt -> not opt.empty?
|
182
|
+
isNothing opt -> opt.empty?
|
183
|
+
fromJust opt -> opt.get
|
184
|
+
fromMaybe default opt -> opt.get_or_else default
|
185
|
+
listToMaybe list -> Option.ary_to_type list
|
186
|
+
maybeToList opt -> opt.to_a
|
187
|
+
catMaybes listOfOptions -> listOfOptions.flatten
|
188
|
+
mapMaybe f list -> list.flat_map &f
|
189
|
+
```
|
190
|
+
|
191
|
+
|
192
|
+
## Development
|
193
|
+
|
194
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
195
|
+
|
196
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
197
|
+
|
198
|
+
|
199
|
+
## Contributing
|
200
|
+
|
201
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lbarasti/yopt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
202
|
+
|
203
|
+
|
204
|
+
## License
|
205
|
+
|
206
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :docs do
|
11
|
+
partial_keyword = '<<<<<'
|
12
|
+
ignore_keyword = "# IGNORE\n"
|
13
|
+
comment_keyword = "# COMMENT\n"
|
14
|
+
src = './docs/README.template.md'
|
15
|
+
target = './README.md'
|
16
|
+
content = File.readlines(src).flat_map {|line|
|
17
|
+
if line.lstrip.start_with?(partial_keyword)
|
18
|
+
partial_file = line.lstrip[partial_keyword.size...-1]
|
19
|
+
sh 'ruby', partial_file
|
20
|
+
File.readlines partial_file
|
21
|
+
else
|
22
|
+
line
|
23
|
+
end
|
24
|
+
}
|
25
|
+
File.open(target, 'w') {|f|
|
26
|
+
content.reject{|line|
|
27
|
+
line.end_with?(ignore_keyword)
|
28
|
+
}.each_cons(2){|line1,line2|
|
29
|
+
next if line1.end_with?(comment_keyword)
|
30
|
+
if line2.end_with?(comment_keyword)
|
31
|
+
f.puts "#{line1.chomp} # #{line2.match(/'(.*)'/)[1]}\n"
|
32
|
+
else
|
33
|
+
f.puts line1
|
34
|
+
end
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :test
|
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
# Yopt
|
2
|
+
|
3
|
+
A [Scala](http://www.scala-lang.org/api/current/index.html#scala.Option) inspired gem that introduces `Option`s to Ruby while aiming for an idiomatic API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'yopt'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install yopt
|
20
|
+
|
21
|
+
## Basic usage
|
22
|
+
|
23
|
+
The Option type models the possible absence of a value. It lets us deal with the uncertainty related to such a value being there without having to resort to errors or conditional blocks.
|
24
|
+
|
25
|
+
Instances of Option are either an instance of `Yopt::Some` - meaning the option contains a value - or the object `Yopt::None` - meaning the option is *empty*.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'yopt'
|
29
|
+
|
30
|
+
some = Yopt::Some.new(42)
|
31
|
+
none = Yopt::None
|
32
|
+
```
|
33
|
+
|
34
|
+
We can access and manipulate the optional value by passing a block to `Option#map`.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
some.map {|value| value + 2} # returns Some(44)
|
38
|
+
none.map {|value| value + 2} # returns None
|
39
|
+
```
|
40
|
+
|
41
|
+
When we are not interested in the result of a computation on the optional value, it is a good practice to use `Option#each` rather than `Option#map`. That will make our intention clearer.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
some.each {|value| puts value} # prints 42
|
45
|
+
none.each {|value| puts value} # does not print anything
|
46
|
+
```
|
47
|
+
|
48
|
+
We can safely retrieve the optional value by passing a default value to `Option#get_or_else`
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
some.get_or_else 0 # returns 42
|
52
|
+
none.get_or_else 0 # returns 0
|
53
|
+
```
|
54
|
+
|
55
|
+
We can also filter the optional value depending on how it evaluates against a block via `Option#select`
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
some.select {|value| value < 0} # returns None
|
59
|
+
none.select {|value| value < 0} # returns None
|
60
|
+
some.select {|value| value > 0} # returns Some(42)
|
61
|
+
```
|
62
|
+
|
63
|
+
We can easily turn any object into an Option by means of `Option.call` - aliased to `Option.[]` for convenience.
|
64
|
+
For instance, this is useful when dealing with functions that might return `nil` to express the absence of a result.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Yopt::Option[nil] # returns None
|
68
|
+
Yopt::Option[42] # returns Some(42)
|
69
|
+
```
|
70
|
+
|
71
|
+
|
72
|
+
A combination of the few methods just introduced already allows us to implement some pretty interesting logic. Checkout `basics.rb` in the docs folder to get some inspiration.
|
73
|
+
|
74
|
+
## Why opt?
|
75
|
+
|
76
|
+
Using `Option`s reduces the amount of branching in our code and lets us deal with exceptional cases in a seamless way. No more check-for-nil, no more `rescue` blocks, just plain and simple data transformation.
|
77
|
+
|
78
|
+
It also makes our code safer by treating *the absence of something* like a fully fledged object, and enables us to use the Null Object Pattern everywhere we want without the overhead of having to write specialized Null-type classes for different classes.
|
79
|
+
|
80
|
+
## Advanced Usage
|
81
|
+
### #reduce
|
82
|
+
Given an Option `opt`, a value `c` and a lambda `f`,
|
83
|
+
```
|
84
|
+
opt.reduce(c, &f)
|
85
|
+
```
|
86
|
+
returns `c` if `opt` is `None`, and `f.(c, opt)` otherwise.
|
87
|
+
|
88
|
+
This is a shortcut to
|
89
|
+
```
|
90
|
+
opt.map{|v| f.(c,v)}.get_or_else(c)`
|
91
|
+
```
|
92
|
+
|
93
|
+
|
94
|
+
### #flatten and #flat_map
|
95
|
+
When working with functions returning `Option`, we might end up dealing with nested options...
|
96
|
+
```ruby
|
97
|
+
maybe_sqrt = lambda {|x| Yopt::Option[x >= 0 ? Math.sqrt(x) : nil]}
|
98
|
+
maybe_increment = lambda {|x| Yopt::Option[x > 1 ? x + 1 : nil]}
|
99
|
+
|
100
|
+
maybe_sqrt.(4).map {|v| maybe_increment.(v)} # Some(Some(3.0))
|
101
|
+
maybe_sqrt.(1).map {|v| maybe_increment.(v)} # Some(None)
|
102
|
+
```
|
103
|
+
|
104
|
+
Usually, this is not what we want, so we call `Option#flatten` on the result
|
105
|
+
```ruby
|
106
|
+
maybe_sqrt.(4).map {|v| maybe_increment.(v)}.flatten # Some(3.0)
|
107
|
+
maybe_sqrt.(1).map {|v| maybe_increment.(v)}.flatten # None
|
108
|
+
```
|
109
|
+
|
110
|
+
`Option#flat_map` combines the two calls into one
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
maybe_sqrt.(4).flat_map {|v| maybe_increment.(v)} # Some(3.0)
|
114
|
+
maybe_sqrt.(1).flat_map {|v| maybe_increment.(v)} # None
|
115
|
+
```
|
116
|
+
|
117
|
+
A difference to keep in mind is that `#flatten` will raise an error if the wrapped value does not respond to `#to_ary`
|
118
|
+
```ruby
|
119
|
+
Yopt.Option[42].flatten # raises TypeError: Argument must be an array-like object. Found Fixnum
|
120
|
+
```
|
121
|
+
whereas #flat_map behaves like #map when the passed block does not return an array-like value
|
122
|
+
```ruby
|
123
|
+
Yopt.Option[42].flat_map{|v| v} # returns Some(42)
|
124
|
+
```
|
125
|
+
|
126
|
+
|
127
|
+
### #zip
|
128
|
+
When dealing with a set of `Option` instances, we might want to ensure that they are all defined - i.e. not __empty__ - before continuing a computation...
|
129
|
+
```ruby
|
130
|
+
email_opt.each(&send_pass_recovery) unless (email_opt.empty? or captcha_opt.empty?)
|
131
|
+
```
|
132
|
+
|
133
|
+
We can avoid `empty?` checks by using `Option#zip`
|
134
|
+
```ruby
|
135
|
+
email_opt.zip(captcha_opt).each{|(email,_)| send_pass_recovery(email)}
|
136
|
+
```
|
137
|
+
|
138
|
+
`Option#zip` returns `None` if any of the arguments is `None` or if the caller is `None`
|
139
|
+
```ruby
|
140
|
+
Yopt::None.zip Option.[42] # None
|
141
|
+
Option.[42].zip Yopt::None # None
|
142
|
+
Option.[42].zip Option.[0], Yopt::None, Option.[-1] # None
|
143
|
+
```
|
144
|
+
|
145
|
+
When both the caller and all the arguments are defined then `zip` collects all the values in an Array wrapped in a `Yopt::Some`
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
Option.[42].zip Option.[0], Option.["str"] # Some([42, 0, "str"])
|
149
|
+
```
|
150
|
+
|
151
|
+
|
152
|
+
### #grep
|
153
|
+
We often find ourselves filtering data before applying a transformation...
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
opt.filter {|v| (1...10).include? v}.map {|v| v + 1}
|
157
|
+
```
|
158
|
+
|
159
|
+
In this scenario, `Option#grep` can sometimes make the code more concise
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
opt.grep(1...10) {|v| v + 1}
|
163
|
+
```
|
164
|
+
|
165
|
+
`Option#grep` supports lambdas as well
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
is_positive = lambda {|x| x > 0}
|
169
|
+
|
170
|
+
opt.grep(is_positive) {|v| Math.log(v)}
|
171
|
+
# is equivalent to
|
172
|
+
opt.filter(&is_positive).map {|v| Math.log(v)}
|
173
|
+
```
|
174
|
+
|
175
|
+
|
176
|
+
## Haskell Data.Maybe cheat sheet
|
177
|
+
|
178
|
+
Some (None?) might enjoy a comparison with Haskell's [Maybe](https://hackage.haskell.org/package/base/docs/Data-Maybe.html). Here is how the Data.Maybe API translate to Yopt.
|
179
|
+
```ruby
|
180
|
+
maybe default f opt -> opt.map(&f).get_or_else(default)
|
181
|
+
isJust opt -> not opt.empty?
|
182
|
+
isNothing opt -> opt.empty?
|
183
|
+
fromJust opt -> opt.get
|
184
|
+
fromMaybe default opt -> opt.get_or_else default
|
185
|
+
listToMaybe list -> Option.ary_to_type list
|
186
|
+
maybeToList opt -> opt.to_a
|
187
|
+
catMaybes listOfOptions -> listOfOptions.flatten
|
188
|
+
mapMaybe f list -> list.flat_map &f
|
189
|
+
```
|
190
|
+
|
191
|
+
|
192
|
+
## Development
|
193
|
+
|
194
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
195
|
+
|
196
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
197
|
+
|
198
|
+
|
199
|
+
## Contributing
|
200
|
+
|
201
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lbarasti/yopt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
202
|
+
|
203
|
+
|
204
|
+
## License
|
205
|
+
|
206
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
207
|
+
|
data/docs/basics.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../test/test_helper' # IGNORE
|
2
|
+
# IGNORE
|
3
|
+
require 'test/unit' # IGNORE
|
4
|
+
include Test::Unit::Assertions # IGNORE
|
5
|
+
# IGNORE
|
6
|
+
class Cache < Hash
|
7
|
+
def maybe_get key
|
8
|
+
Yopt::Option[self[key]]
|
9
|
+
end
|
10
|
+
|
11
|
+
def store_if_some key, opt_value
|
12
|
+
opt_value.each {|value| self.store(key, value)}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def maybe_sqrt value
|
17
|
+
opt = Yopt::Option[value]
|
18
|
+
opt.select {|value| value >= 0}
|
19
|
+
.map {|value| Math.sqrt(value)}
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_info cache, key
|
23
|
+
cache.maybe_get(key)
|
24
|
+
.map {|value| "found value %.2f for key %s" % [value, key]}
|
25
|
+
.get_or_else "value not found for key #{key}"
|
26
|
+
end
|
27
|
+
|
28
|
+
cache = Cache.new
|
29
|
+
|
30
|
+
cache.store_if_some 42, maybe_sqrt(42)
|
31
|
+
|
32
|
+
key = 42 + rand(2) # could be either 42 or 43
|
33
|
+
|
34
|
+
puts get_info(cache, key)
|
35
|
+
# IGNORE
|
36
|
+
same_string(cache.maybe_get(key)).(Yopt::Some.new(Math.sqrt(42)).to_s) if key == 42 # IGNORE
|
37
|
+
same_string(cache.maybe_get(key)).(Yopt::None.to_s) if key != 42 # IGNORE
|
@@ -0,0 +1,21 @@
|
|
1
|
+
## About Enumerable methods
|
2
|
+
By including `Enumerable`, `Option` gives access to a set of methods which are either redundant or not really meaningful for a `Some` or a `None` object
|
3
|
+
|
4
|
+
```ruby
|
5
|
+
:slice_after :slice_before :slice_when :take :take_while :drop :drop_while :chunk :sort_by :each_entry :each_slice :group_by :minmax_by :sort :each_with_index :partition :find_all :max_by :min_by :reverse_each :each_cons :min :max :minmax
|
6
|
+
```
|
7
|
+
|
8
|
+
On the other hand, all the methods returning a boolean work as expected
|
9
|
+
```ruby
|
10
|
+
:all? :any? :include? :none? :one? :member?
|
11
|
+
```
|
12
|
+
|
13
|
+
The following methods work in a predictable way out-of-the-box
|
14
|
+
```ruby
|
15
|
+
:reduce :inject :each_with_object :cycle :to_set :find_index :find :detect :first :count :to_a :entries :to_h
|
16
|
+
```
|
17
|
+
|
18
|
+
While the following methods have custom definition to always return an `Option` rather than an `Array`
|
19
|
+
```ruby
|
20
|
+
:collect :map :flat_map :collect_concat :reject :select :zip :grep
|
21
|
+
```
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../test/test_helper' # IGNORE
|
2
|
+
# IGNORE
|
3
|
+
require 'test/unit' # IGNORE
|
4
|
+
include Test::Unit::Assertions # IGNORE
|
5
|
+
|
6
|
+
name2phone = [[:a, "+1 310-000-001"],
|
7
|
+
[:b, "+1 323-000-002"],
|
8
|
+
[:d, "+1 310-000-003"],
|
9
|
+
[:e, "+1 213-000-004"],
|
10
|
+
[:f, "+1 323-000-005"],
|
11
|
+
[:h, "+1 213-000-006"],
|
12
|
+
[:k, "+1 213-000-007"],
|
13
|
+
[:s, "+1 310-000-009"],
|
14
|
+
[:w, "+1 800-000-010"]]
|
15
|
+
|
16
|
+
phone2postcode = [["+1 310-000-001", "CA 90210"],
|
17
|
+
["+1 310-000-003", "CA 90210"],
|
18
|
+
["+1 323-000-005", "CA 90028"],
|
19
|
+
["+1 213-000-006", "CA 90027"],
|
20
|
+
["+1 213-000-007", "CA 90027"],
|
21
|
+
["+1 800-000-010", "CA 91608"]]
|
22
|
+
|
23
|
+
postcode2income = [["CA 90210", "$80000"],
|
24
|
+
["CA 91608", "$65000"]]
|
25
|
+
|
26
|
+
|
27
|
+
lookup = -> (table, key) {
|
28
|
+
row_or_nil = table.find { |row| row.first == key }
|
29
|
+
Yopt::Option[row_or_nil] | :last
|
30
|
+
}
|
31
|
+
|
32
|
+
same_string( # IGNORE
|
33
|
+
lookup.(name2phone, :s)
|
34
|
+
).('Some(+1 310-000-009)') # COMMENT
|
35
|
+
|
36
|
+
same_string( # IGNORE
|
37
|
+
lookup.(name2phone, :x)
|
38
|
+
).('None') # COMMENT
|
39
|
+
|
40
|
+
same_string( # IGNORE
|
41
|
+
same_string( # IGNORE
|
42
|
+
same_string( # IGNORE
|
43
|
+
lookup.(name2phone, :a)
|
44
|
+
).('Some(+1 310-000-001)') # COMMENT
|
45
|
+
.flat_map {|phone| lookup.(phone2postcode, phone)}
|
46
|
+
).('Some(CA 90210)') # COMMENT
|
47
|
+
.flat_map {|postcode| lookup.(postcode2income, postcode)}
|
48
|
+
).('Some($80000)') # COMMENT
|
49
|
+
|
50
|
+
# or if you want to go crazy-functional
|
51
|
+
lookup.(name2phone, :a) | lookup.curry[phone2postcode] | lookup.curry[postcode2income]
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Some might find that having to specify the scope every time we want to use `Some` or `None` a bit too verbose. If that is the case, please consider that module scoping is a good receipe to avoid hideous naming clashes.
|
2
|
+
|
3
|
+
If you understand the risk then you can either `include Yopt` or cherry-pick the tools you need from the module like so:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
Some = Yopt::Some
|
7
|
+
None = Yopt::None
|
8
|
+
```
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'yopt'
|
2
|
+
|
3
|
+
compute_increase = Yopt.lift {|user| 10000 if ['Bob', 'Joe', 'Eve'].member?(user)}
|
4
|
+
|
5
|
+
base_salary = 20000
|
6
|
+
user = ['Noel', 'Eve'].sample
|
7
|
+
salary_increase_opt = compute_increase.(user) # None | Some(10000)
|
8
|
+
|
9
|
+
salary_increase_opt.reduce(base_salary, &:+) # 20000 | 30000
|
10
|
+
# is equivalent to
|
11
|
+
salary_increase_opt.map{|increase| base_salary + increase} # None | Some(30000)
|
12
|
+
.get_or_else(base_salary) # 20000 | 30000
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative '../test/test_helper' # IGNORE
|
2
|
+
# IGNORE
|
3
|
+
require 'test/unit' # IGNORE
|
4
|
+
include Test::Unit::Assertions # IGNORE
|
5
|
+
|
6
|
+
# You can create an option from any object `obj`
|
7
|
+
# In general, any object gets wrapped into a `Some` instance
|
8
|
+
some = Yopt::Option[42] # Some(42)
|
9
|
+
# The only exception being `nil`, which turns into `None`
|
10
|
+
none = Yopt::Option[nil] # None
|
11
|
+
|
12
|
+
# since Some and None have the same API, let's choose one of the two
|
13
|
+
# randomly, and record the intermediate result of each computation
|
14
|
+
# for both None and Some(42) following the convention <result_if_none> | <result_if_some>
|
15
|
+
opt = [none, some].sample
|
16
|
+
|
17
|
+
opt # None | Some(42)
|
18
|
+
.select{|x| x > 0} # None | Some(42)
|
19
|
+
.map(&:succ) # None | Some(43)
|
20
|
+
.get_or_else(1) # 1 | 43
|
21
|
+
|
22
|
+
# Calling select/reject on a Some instance can return None
|
23
|
+
# Whereas a None can never be transformed into a Some
|
24
|
+
# None | Some(42)
|
25
|
+
opt.select{ |x| x < 0 } # None | None
|
26
|
+
|
27
|
+
# One of the perks of working with options is that it allow us to stay
|
28
|
+
# away from `nil` and `if` statements. You can still revert to using them if you need to though
|
29
|
+
# There always is a better way to go though.
|
30
|
+
opt.or_nil # nil | 42
|
31
|
+
# Even better, if the option is None we can return a default value straight away
|
32
|
+
opt.get_or_else 31 # 31 | 42
|
33
|
+
|
34
|
+
# If we intend to stay in the Option domain for some more time we can swap None with an other Option
|
35
|
+
opt.or_else(Yopt::Option[61]) # Some(61) | Some(42)
|
36
|
+
.reject(&:even?) # Some(61) | None
|
37
|
+
.get_or_else 0 # 61 | 0
|
38
|
+
|
39
|
+
# Once in the Option domain, it's nice to avoid accessing
|
40
|
+
# the content of an option explicitly as far as possible.
|
41
|
+
# You might eventually need to extract the value wrapped by
|
42
|
+
# Option.
|
43
|
+
begin # None | Some(42)
|
44
|
+
opt.get # raises a RuntimeError | 42
|
45
|
+
rescue RuntimeError
|
46
|
+
puts "cannot call #get on #{opt}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# This does not look safe. Luckily we can check if the option
|
50
|
+
# is None by calling `empty?` on it
|
51
|
+
opt.empty? # true | false
|
52
|
+
|
53
|
+
|
54
|
+
# v = s2.reduce(2){|default, opt_val| opt_val - default} # 47
|
55
|
+
# n = Yopt::Some.new(v) # Some(47)
|
56
|
+
# .collect{|x| if x % 2 == 1 then x + 1 end} # None
|
57
|
+
# .map{|x| x ** 2} # None
|
58
|
+
|
59
|
+
# if s1.reduce(s2.get, &:-) < 0 && s1.include?(42) # true
|
60
|
+
# s2.collect{ n.empty? && n } # Some(None)
|
61
|
+
# .flatten # None
|
62
|
+
# .or_nil # nil
|
63
|
+
# end
|
64
|
+
# head_opt = Util.lift &:first
|
65
|
+
# old_school_validate = -> email {if email.end_with?('my-domain.com') then email else nil end}
|
66
|
+
# valid = 'user@my-domain.com'
|
67
|
+
# invalid = '@gmail.com'
|
68
|
+
# old_school_validate.(invalid).must_equal nil
|
69
|
+
# old_school_validate.(valid).wont_be_nil
|
70
|
+
# old_school_validate.(valid).must_equal valid
|
71
|
+
|
72
|
+
# brand_new_validate = Util.lift &old_school_validate
|
73
|
+
# brand_new_validate.(invalid).must_equal None
|
74
|
+
# brand_new_validate.(valid).must_equal Some.new(valid)
|
75
|
+
|
76
|
+
# user = brand_new_validate.(valid) | Util.lift{|email| email.split('@')[0]} | :upcase
|
77
|
+
# user.get.must_equal "USER"
|
data/docs/usage_zip.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../test/test_helper' # IGNORE
|
2
|
+
# IGNORE
|
3
|
+
require 'test/unit' # IGNORE
|
4
|
+
include Test::Unit::Assertions # IGNORE
|
5
|
+
|
6
|
+
validate_email = Yopt.lift {|x| x if x.end_with? "@domain.com"}
|
7
|
+
validate_password = Yopt.lift {|x| x if x.size > 8}
|
8
|
+
hash_f = lambda {|str| str.each_char.map(&:ord).reduce(:+)}
|
9
|
+
|
10
|
+
# returns Yopt::Some(Fixnum) if email and password are both valid
|
11
|
+
# returns Yopt::None otherwise
|
12
|
+
hash_credentials = -> (email, password) do
|
13
|
+
maybe_email = validate_email.(email)
|
14
|
+
maybe_password = validate_password.(password)
|
15
|
+
|
16
|
+
maybe_email.zip(maybe_password)
|
17
|
+
.map {|(valid_email, valid_pass)| hash_f.(valid_email + valid_pass)}
|
18
|
+
end
|
19
|
+
|
20
|
+
same_string( # IGNORE
|
21
|
+
hash_credentials.('invalid_mail', 'valid_pass')
|
22
|
+
).('None') # COMMENT
|
23
|
+
# IGNORE
|
24
|
+
same_string( # IGNORE
|
25
|
+
hash_credentials.('valid@domain.com', 'invalid')
|
26
|
+
).('None') # COMMENT
|
27
|
+
# IGNORE
|
28
|
+
same_string( # IGNORE
|
29
|
+
hash_credentials.('valid@domain.com', 'valid_pass')
|
30
|
+
).('Some(2651)') # COMMENT
|
data/lib/yopt.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'yopt/version'
|
2
|
+
|
3
|
+
module Yopt
|
4
|
+
def self.lift &block
|
5
|
+
block or raise ArgumentError, 'missing block'
|
6
|
+
-> (*args, &other_block) {Option.(block.call *args, &other_block)}
|
7
|
+
end
|
8
|
+
module Option
|
9
|
+
include Enumerable
|
10
|
+
def self.ary_to_type value
|
11
|
+
raise Option.invalid_argument('an array-like object', value) unless value.respond_to? :to_ary
|
12
|
+
return value if value.is_a? Option
|
13
|
+
if value.to_ary.empty? then None else Some.new(value.to_ary.first) end
|
14
|
+
end
|
15
|
+
def self.call(value)
|
16
|
+
if value.nil? then None else Some.new(value) end
|
17
|
+
end
|
18
|
+
singleton_class.send(:alias_method, :[], :call)
|
19
|
+
def each &block
|
20
|
+
to_ary.each &block
|
21
|
+
end
|
22
|
+
%i(map flat_map select reject collect collect_concat).each do |method|
|
23
|
+
define_method method, ->(&block) {
|
24
|
+
block or return enum_for(method)
|
25
|
+
Option.ary_to_type super(&block)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
def grep(pattern, &block)
|
29
|
+
Option.ary_to_type super
|
30
|
+
end
|
31
|
+
def flatten
|
32
|
+
return self if empty?
|
33
|
+
Option.ary_to_type self.get
|
34
|
+
end
|
35
|
+
def zip *others
|
36
|
+
return None if self.empty? || others.any?(&:empty?)
|
37
|
+
collection = others.reduce(self.to_a, &:concat)
|
38
|
+
Some.new collection
|
39
|
+
end
|
40
|
+
def | lambda
|
41
|
+
self.flat_map &lambda # slow but easy to read + supports symbols out of the box
|
42
|
+
end
|
43
|
+
def ^ lambda
|
44
|
+
self | Yopt.lift(&lambda)
|
45
|
+
end
|
46
|
+
def or_else other
|
47
|
+
raise Option.invalid_argument('an Option', other) unless other.is_a? Option
|
48
|
+
if empty? then other else self end
|
49
|
+
end
|
50
|
+
def get_or_else default
|
51
|
+
if empty? then default else self.get end
|
52
|
+
end
|
53
|
+
def or_nil
|
54
|
+
get_or_else nil
|
55
|
+
end
|
56
|
+
def inspect() to_s end
|
57
|
+
private
|
58
|
+
def self.invalid_argument type_str, arg
|
59
|
+
TypeError.new "Argument must be #{type_str}. Found #{arg.class}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
class Some
|
63
|
+
include Option
|
64
|
+
def initialize value
|
65
|
+
@value = value.freeze
|
66
|
+
end
|
67
|
+
def get() @value end
|
68
|
+
def empty?() false end
|
69
|
+
def to_s() "Some(#{get})" end
|
70
|
+
def to_ary() [get] end
|
71
|
+
def == other
|
72
|
+
other.is_a?(Some) && self.get == other.get
|
73
|
+
end
|
74
|
+
def === other
|
75
|
+
other.is_a?(Some) && self.get === other.get
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class NoneClass
|
80
|
+
include Option
|
81
|
+
def get() raise "Cannot call ##{__method__} on #{self}" end
|
82
|
+
def empty?() true end
|
83
|
+
def to_s() 'None' end
|
84
|
+
def to_ary() [] end
|
85
|
+
end
|
86
|
+
|
87
|
+
None = NoneClass.new
|
88
|
+
end
|
data/lib/yopt/version.rb
ADDED
data/yopt.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'yopt/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "yopt"
|
8
|
+
spec.version = Yopt::VERSION
|
9
|
+
spec.authors = ["lorenzo.barasti"]
|
10
|
+
|
11
|
+
spec.summary = %q{Scala-inspired Options for the idiomatic Rubyist.}
|
12
|
+
spec.description = %q{This gem makes it possible to adopt the Option pattern in Ruby. It's meant to make conditional flow in our software clearer and more linear.}
|
13
|
+
spec.homepage = "https://github.com/lbarasti/yopt"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yopt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- lorenzo.barasti
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-23 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.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: This gem makes it possible to adopt the Option pattern in Ruby. It's
|
56
|
+
meant to make conditional flow in our software clearer and more linear.
|
57
|
+
email:
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- CODE_OF_CONDUCT.md
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- docs/README.template.md
|
72
|
+
- docs/basics.rb
|
73
|
+
- docs/enumerable_methods.md
|
74
|
+
- docs/full_example_snippet.rb
|
75
|
+
- docs/including yopt.md
|
76
|
+
- docs/usage_reduce.rb
|
77
|
+
- docs/usage_snippet.rb
|
78
|
+
- docs/usage_zip.rb
|
79
|
+
- lib/yopt.rb
|
80
|
+
- lib/yopt/version.rb
|
81
|
+
- yopt.gemspec
|
82
|
+
homepage: https://github.com/lbarasti/yopt
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.4.5
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Scala-inspired Options for the idiomatic Rubyist.
|
106
|
+
test_files: []
|
107
|
+
has_rdoc:
|