dux 0.3.0 → 0.8.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 +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 [](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
|