dux 0.3.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -4
- data/.yardopts +3 -0
- data/README.md +188 -11
- data/dux.gemspec +2 -2
- data/lib/dux.rb +13 -169
- data/lib/dux/blankness.rb +9 -2
- data/lib/dux/comparable.rb +241 -0
- data/lib/dux/duckify.rb +1 -13
- data/lib/dux/enum.rb +194 -11
- data/lib/dux/indifferent_string.rb +9 -1
- data/lib/dux/inspect_id.rb +6 -1
- data/lib/dux/monkey_patch.rb +97 -0
- data/lib/dux/null_object.rb +52 -5
- data/lib/dux/predicate.rb +139 -0
- data/lib/dux/version.rb +1 -1
- metadata +7 -5
- data/lib/dux/duck_type.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed22ad66960e4fd9f7c8616f2541a0363a2ecf40
|
4
|
+
data.tar.gz: 40d5a91736ca7ca6a0e61e3508c1fa0fdf94b3b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cb5fb4869324b0d8d9a2cf225d78e24b6c2de3315bf8917fe07b62d6528d01f1245d9dd6f498fd8e9a5e0a7956bdb2c2ad5ecd00238bea69c51097f78f67a0f
|
7
|
+
data.tar.gz: f5ce999d6fb1ab0f9d440108762167f8b0a2cd6b1461c8ae3e8ba69ea75a0a24de62e7d4f8adb7b42c5251a3ddc4a3a1439f4be6dda8919b04f1b72e5f3dfc59
|
data/.travis.yml
CHANGED
data/.yardopts
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Dux [![Build Status](https://travis-ci.org/scryptmouse/dux.svg?branch=master)](https://travis-ci.org/scryptmouse/dux)
|
2
2
|
|
3
|
-
A
|
3
|
+
A swiss-army knife of duck-type and utility objects without monkey-patching.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -18,27 +18,204 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install dux
|
20
20
|
|
21
|
-
##
|
21
|
+
## Dux[] / Predicates
|
22
22
|
|
23
|
-
|
23
|
+
`Dux[]` can be used for creating predicate matchers for case equality.
|
24
|
+
It will check if whatever it is compared against can `respond_to?` the
|
25
|
+
symbol it's provided with.
|
26
|
+
|
27
|
+
Usage is straightforward:
|
24
28
|
|
25
29
|
```ruby
|
26
30
|
case foo
|
27
|
-
when Dux[:bar]
|
28
|
-
|
29
|
-
when Dux[:
|
31
|
+
when Dux[:bar]
|
32
|
+
foo.bar
|
33
|
+
when Dux[:baz]
|
34
|
+
foo.baz
|
35
|
+
when Dux[:quux]
|
36
|
+
foo.quux
|
37
|
+
else
|
38
|
+
raise TypeError, "Dunno what do to do with #{foo.inspect}"
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Because it uses lambdas under the hood, it can also be used
|
43
|
+
as an enumerable predicate:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
list_of_objects_that_should_conform.all?(&Dux[:some_method])
|
47
|
+
```
|
48
|
+
|
49
|
+
That's not the intended usage, but it works.
|
50
|
+
|
51
|
+
### Inheritance / Inclusion / Extension
|
52
|
+
|
53
|
+
There are also predicates for testing class structure:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class SomeClass < SomeParentClass
|
57
|
+
extend SomeDSLMethods
|
58
|
+
include SomeHelpers
|
59
|
+
prepend SomeOverrides
|
30
60
|
end
|
61
|
+
|
62
|
+
Dux.inherits(SomeParentClass) === SomeClass # => true
|
63
|
+
Dux.inherits(SomeHelpers) === SomeClass # => true
|
64
|
+
Dux.inherits(SomeOverrides) === SomeClass # => true
|
65
|
+
|
66
|
+
# If you want to use Module#<= in the predicate, use include_self: true
|
67
|
+
Dux.inherits(SomeClass, include_self: true) === SomeClass # => true
|
68
|
+
|
69
|
+
# Testing for module extension is different.
|
70
|
+
Dux.extends(SomeDSLMethods) === SomeClass # => true
|
31
71
|
```
|
32
72
|
|
33
|
-
|
73
|
+
`Dux.inherits` is aliased to `Dux.prepends` and `Dux.includes`
|
74
|
+
for semantic clarity, but it does not currently check
|
75
|
+
if a module was included or prepended.
|
76
|
+
|
77
|
+
### Interfaces
|
78
|
+
|
79
|
+
There are also some methods for matching against a strict / flexible interface:
|
34
80
|
|
35
81
|
```ruby
|
36
82
|
case foo
|
37
|
-
when Dux.all(:bar, :baz)
|
38
|
-
|
83
|
+
when Dux.all(:bar, :baz)
|
84
|
+
foo.bar && foo.baz
|
85
|
+
when Dux.any(:quux, :bloop)
|
86
|
+
Dux.attempt(foo, :quux) || Dux.attempt(foo, :bloop)
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
### YARD Types
|
91
|
+
|
92
|
+
If you have a condition more easily expressed in [YARD types](http://yardoc.org/types),
|
93
|
+
you can do something like the following:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
case some_overloaded_arg
|
97
|
+
when Dux.yard('(Symbol, Symbol)')
|
98
|
+
# Do something with a tuple of two symbols
|
99
|
+
when Dux.yard('{ Symbol => <Symbol> }')
|
100
|
+
# Do something with a symbol-keyed hash with
|
101
|
+
# values that are an array of symbols
|
102
|
+
when Dux.yard('<SomeClass>')
|
103
|
+
# Do something with an array of `SomeClass` members
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
Performance of the underlying gem is not thoroughly tested,
|
108
|
+
but it should work just fine for non-intensive use cases.
|
109
|
+
|
110
|
+
## Dux.comparable
|
111
|
+
|
112
|
+
Simplifies creating comparable objects when just want
|
113
|
+
to compare on a couple of attributes defined on objects.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Person < Struct.new(:name)
|
117
|
+
include Dux.comparable :name
|
118
|
+
end
|
119
|
+
|
120
|
+
alice = Person.new 'Alice'
|
121
|
+
bob = Person.new 'Bob'
|
122
|
+
carol = Person.new 'Carol'
|
123
|
+
|
124
|
+
[bob, carol, alice].sort == [alice, bob, carol]
|
125
|
+
|
126
|
+
# You can also sort descending:
|
127
|
+
|
128
|
+
Person.include Dux.comparable :name, sort_order: :desc
|
129
|
+
|
130
|
+
[alice, carol, bob].sort == [carol, bob, alice]
|
131
|
+
```
|
132
|
+
|
133
|
+
You can additionally specify multiple attributes with
|
134
|
+
individual sort ordering for each attribute:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
class Person < Struct.new(:name, :salary)
|
138
|
+
include Dux.comparable [:salary, :desc], :name
|
139
|
+
end
|
140
|
+
|
141
|
+
alice = Person.new 'Alice', 100_000
|
142
|
+
bob = Person.new 'Bob', 75_000
|
143
|
+
carol = Person.new 'Carol', 100_000
|
144
|
+
|
145
|
+
[carol, bob, alice].sort == [alice, carol, bob]
|
146
|
+
```
|
147
|
+
|
148
|
+
## Dux.enum
|
149
|
+
|
150
|
+
Create an indifferent set of strings/symbols that can be used
|
151
|
+
to validate options.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
ROLES = Dux.enum :author, :admin, :reader
|
155
|
+
|
156
|
+
ROLES[:author] # => :author
|
157
|
+
ROLES[:nonexistent] # raises Dux::Enum::NotFound
|
158
|
+
|
159
|
+
ROLES.fetch :nonexistent do |value|
|
160
|
+
raise YourErrorHere, "Invalid role: #{value}"
|
39
161
|
end
|
40
162
|
```
|
41
163
|
|
164
|
+
If you want a specific fallback value instead of raising an error, you can do that too
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
ROLES = Dux.enum :author, :admin, :reader, default: :reader
|
168
|
+
|
169
|
+
ROLES[:nonexistent] # => :reader
|
170
|
+
|
171
|
+
# Override the fallback for a particular fetch
|
172
|
+
ROLES[:nonexistent, fallback: :author] # => :author
|
173
|
+
```
|
174
|
+
|
175
|
+
## Utilities
|
176
|
+
|
177
|
+
Small utility methods, many to replace needing ActiveSupport / monkey patching.
|
178
|
+
|
179
|
+
### Dux.attempt
|
180
|
+
|
181
|
+
`Object#try` when you don't have/want ActiveSupport.
|
182
|
+
|
183
|
+
It will attempt to execute a provided method (with optional args and block)
|
184
|
+
with `public_send`, or simply return `nil` if it doesn't respond.
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
Dux.attempt(some_object, :method, *args, &block)
|
188
|
+
```
|
189
|
+
|
190
|
+
### Dux.blankish? / Dux.presentish?
|
191
|
+
|
192
|
+
`Object#blank?` & `Object#present?` when you don't have/want ActiveSupport.
|
193
|
+
|
194
|
+
Rather than being monkey patched across all `Object`s, you will need to
|
195
|
+
use `Dux` to check:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
Dux.blankish? [nil] # => true
|
199
|
+
Dux.blankish? Hash.new # => true
|
200
|
+
Dux.blankish? "\t" # => true
|
201
|
+
Dux.blankish? Float::NAN
|
202
|
+
```
|
203
|
+
|
204
|
+
### Dux.inspect_id
|
205
|
+
|
206
|
+
If you are overriding the `#inspect` method on an object and want
|
207
|
+
to keep that unique hex `object_id`, this offers a shorthand:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
def inspect
|
211
|
+
"#<YourClassHere:#{Dux.inspect_id(self)}>"
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
## Monkey patches / Experimental
|
216
|
+
|
217
|
+
These will probably end up getting removed for version 1.x.
|
218
|
+
|
42
219
|
### Core Extensions
|
43
220
|
|
44
221
|
There are a few core extensions available that can be manually enabled.
|
@@ -75,7 +252,7 @@ when %i[quux bloop].duckify(type: :any) then foo.try(:quux) || foo.try(:bloop)
|
|
75
252
|
end
|
76
253
|
```
|
77
254
|
|
78
|
-
#### Shorthand
|
255
|
+
#### ~ Shorthand
|
79
256
|
|
80
257
|
There is an experimental option that uses unary `~` as an alias for `#duckify`.
|
81
258
|
|
@@ -116,7 +293,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
116
293
|
|
117
294
|
## Supported Ruby Versions
|
118
295
|
|
119
|
-
* MRI 2.
|
296
|
+
* MRI 2.2+
|
120
297
|
* Rubinius 2.5+
|
121
298
|
* jruby-head / 9.0+
|
122
299
|
|
data/dux.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Alexa Grey"]
|
10
10
|
spec.email = ["devel@mouse.vc"]
|
11
11
|
|
12
|
-
spec.summary = %q{
|
12
|
+
spec.summary = %q{Swiss-army knife gem for duck-type matching and utility methods}
|
13
13
|
spec.homepage = "https://github.com/scryptmouse/dux"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.metadata["yard.run"] = "yri"
|
22
22
|
|
23
|
-
spec.add_dependency "
|
23
|
+
spec.add_dependency "yard_types"
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.9"
|
26
26
|
spec.add_development_dependency "rspec", "~> 3.5"
|
data/lib/dux.rb
CHANGED
@@ -1,183 +1,27 @@
|
|
1
1
|
require 'delegate'
|
2
|
+
require 'strscan'
|
3
|
+
require 'yard_types'
|
2
4
|
|
3
5
|
require 'dux/version'
|
6
|
+
|
4
7
|
require 'dux/inspect_id'
|
5
8
|
require 'dux/null_object'
|
6
9
|
require 'dux/attempt'
|
7
10
|
require 'dux/blankness'
|
8
|
-
require 'dux/duckify'
|
9
|
-
require 'dux/hacks_like_a_duck'
|
10
|
-
require 'dux/flock_methods'
|
11
|
-
|
12
11
|
require 'dux/indifferent_string'
|
13
|
-
require 'dux/enum'
|
14
|
-
|
15
|
-
# Super simple duck-type testing.
|
16
|
-
module Dux
|
17
|
-
# Methods for generating "interface" checks against an array of symbols
|
18
|
-
FLOCK_TYPES = %i[all any none]
|
19
|
-
|
20
|
-
module_function
|
21
|
-
|
22
|
-
# @param [Symbol] symbol
|
23
|
-
# @param [Boolean] include_all
|
24
|
-
# @return [Proc]
|
25
|
-
def dux(symbol, include_all: false)
|
26
|
-
->(obj) { obj.respond_to? symbol, include_all }
|
27
|
-
end
|
28
|
-
|
29
|
-
class << self
|
30
|
-
alias_method :[], :dux
|
31
|
-
|
32
|
-
# @param [:all, :any, :none] type
|
33
|
-
# @param [<Symbol, String>] methods
|
34
|
-
# @param [Boolean] include_all
|
35
|
-
# @raise [ArgumentError] on invalid type
|
36
|
-
# @return [<Proc>]
|
37
|
-
def flock(type, *methods, include_all: false)
|
38
|
-
raise ArgumentError, "Invalid flock type: #{type}" unless FLOCK_TYPES.include? type
|
39
|
-
|
40
|
-
__send__ type, methods, include_all: include_all
|
41
|
-
end
|
42
|
-
|
43
|
-
# Creates a lambda that describes a restrictive interface,
|
44
|
-
# wherein the provided object must `respond_to?` *all* of
|
45
|
-
# the methods.
|
46
|
-
#
|
47
|
-
# @param [<Symbol, String>] methods
|
48
|
-
# @param [Boolean] include_all
|
49
|
-
# @return [Proc]
|
50
|
-
def all(*methods, include_all: false)
|
51
|
-
ducks = flockify methods, include_all: include_all
|
52
|
-
|
53
|
-
->(obj) { ducks.all? { |duck| duck.call obj } }
|
54
|
-
end
|
55
|
-
|
56
|
-
# Creates a lambda that describes a permissive interface,
|
57
|
-
# wherein the provided object must `respond_to?` at least
|
58
|
-
# one of the methods.
|
59
|
-
#
|
60
|
-
# @param [<Symbol>] methods
|
61
|
-
# @param [Boolean] include_all
|
62
|
-
# @return [Proc]
|
63
|
-
def any(*methods, include_all: false)
|
64
|
-
ducks = flockify methods, include_all: include_all
|
65
|
-
|
66
|
-
->(obj) { ducks.any? { |duck| duck.call obj } }
|
67
|
-
end
|
68
|
-
|
69
|
-
# Creates a lambda that describes a restrictive interface,
|
70
|
-
# wherein the provided object must `respond_to?` *none* of
|
71
|
-
# the methods.
|
72
|
-
#
|
73
|
-
# @param [<Symbol>] methods
|
74
|
-
# @param [Boolean] include_all
|
75
|
-
# @return [Proc]
|
76
|
-
def none(*methods, include_all: false)
|
77
|
-
ducks = flockify methods, include_all: include_all
|
78
|
-
|
79
|
-
->(obj) { ducks.none? { |duck| duck.call obj } }
|
80
|
-
end
|
81
|
-
|
82
|
-
# @!group Core extensions
|
83
|
-
|
84
|
-
# Enhance `Array` with {Dux::FlockMethods#duckify}
|
85
|
-
#
|
86
|
-
# @return [void]
|
87
|
-
def add_flock_methods!
|
88
|
-
Array.__send__ :prepend, Dux::FlockMethods
|
89
12
|
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
# Load all `Dux` core extensions.
|
94
|
-
#
|
95
|
-
# @param [Boolean] experimental whether to load experimental shorthand methods
|
96
|
-
# @return [void]
|
97
|
-
def extend_all!(experimental: false)
|
98
|
-
add_flock_methods!
|
99
|
-
extend_strings_and_symbols!
|
100
|
-
|
101
|
-
return unless experimental
|
102
|
-
|
103
|
-
array_shorthand!
|
104
|
-
string_shorthand!
|
105
|
-
symbol_shorthand!
|
106
|
-
end
|
107
|
-
|
108
|
-
# Enhance `String` with {Dux::Duckify}
|
109
|
-
#
|
110
|
-
# @return [void]
|
111
|
-
def extend_strings!
|
112
|
-
String.__send__ :prepend, Dux::Duckify
|
113
|
-
|
114
|
-
return nil
|
115
|
-
end
|
116
|
-
|
117
|
-
# Enhance `Symbol` with {Dux::Duckify}
|
118
|
-
#
|
119
|
-
# @return [void]
|
120
|
-
def extend_symbols!
|
121
|
-
Symbol.__send__ :prepend, Dux::Duckify
|
122
|
-
|
123
|
-
return nil
|
124
|
-
end
|
125
|
-
|
126
|
-
# Enhance `String` and `Symbol` classes with {Dux::Duckify#duckify}
|
127
|
-
#
|
128
|
-
# @return [void]
|
129
|
-
def extend_strings_and_symbols!
|
130
|
-
extend_strings!
|
131
|
-
extend_symbols!
|
132
|
-
|
133
|
-
return nil
|
134
|
-
end
|
135
|
-
|
136
|
-
# Experimental feature to add unary `~` to `Array`s
|
137
|
-
# for quick usage in case statements.
|
138
|
-
#
|
139
|
-
# @return [void]
|
140
|
-
def array_shorthand!
|
141
|
-
add_flock_methods!
|
142
|
-
|
143
|
-
Array.__send__ :prepend, Dux::HacksLikeADuck
|
144
|
-
|
145
|
-
nil
|
146
|
-
end
|
147
|
-
|
148
|
-
# Experimental feature to add unary `~` to `String`s
|
149
|
-
# for quick usage in case statements.
|
150
|
-
#
|
151
|
-
# @return [void]
|
152
|
-
def string_shorthand!
|
153
|
-
extend_strings!
|
154
|
-
String.__send__ :prepend, Dux::HacksLikeADuck
|
155
|
-
|
156
|
-
nil
|
157
|
-
end
|
158
|
-
|
159
|
-
# Experimental feature to add unary `~` to `Symbol`s
|
160
|
-
# for quick usage in case statements.
|
161
|
-
#
|
162
|
-
# @return [void]
|
163
|
-
def symbol_shorthand!
|
164
|
-
extend_symbols!
|
13
|
+
require 'dux/enum'
|
165
14
|
|
166
|
-
|
15
|
+
require 'dux/predicate'
|
167
16
|
|
168
|
-
|
169
|
-
end
|
17
|
+
require 'dux/comparable'
|
170
18
|
|
171
|
-
|
19
|
+
# Experimental nonsense
|
20
|
+
require 'dux/monkey_patch'
|
21
|
+
require 'dux/duckify'
|
22
|
+
require 'dux/hacks_like_a_duck'
|
23
|
+
require 'dux/flock_methods'
|
172
24
|
|
173
|
-
|
174
|
-
|
175
|
-
# @param [Boolean] include_all
|
176
|
-
# @return [<Proc>]
|
177
|
-
def flockify(methods, include_all: false)
|
178
|
-
methods.flatten.map do |sym|
|
179
|
-
dux sym, include_all: include_all
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
25
|
+
# Swiss-army duck type matching
|
26
|
+
module Dux
|
183
27
|
end
|